From 4ce6f593690089d72b612e3a6876c6b637961610 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 21:28:47 -0400 Subject: [PATCH 1/5] feat(exact-one-SAT): prove parsimonious positive SAT completeness Refs #1257. Positive exact-three clauses use distinct variables; the strongest target permits at most three occurrences per variable. Counts range over occurring variables. Both local replacements have explicit constant-factor bounds and native polynomial-time certificates. Co-developed with Codex workers t3-96187517-positive-semantics and t3-96187517-positive-gadgets. Final exact-revision validation and coordinator review will be recorded in the PR; the earlier combined working-tree six-gate run passed. Selected formal abstracts and rendering are unchanged. --- Complexity.lean | 11 + .../Complexity/Problems/OneInThreeSAT.lean | 378 +++++++++++ .../Problems/OneInThreeSAT/Reduction.lean | 214 ++++++ .../Logics/Propositional/CNF/ExactOne.lean | 231 +++++++ .../CNF/ExactOne/BlockBounds.lean | 87 +++ .../Propositional/CNF/ExactOne/Blocks.lean | 185 +++++ .../Propositional/CNF/ExactOne/Compile.lean | 172 +++++ .../Propositional/CNF/ExactOne/FromSAT.lean | 393 +++++++++++ .../Propositional/CNF/ExactOne/Gadgets.lean | 241 +++++++ .../CNF/ExactOne/ModelCount.lean | 94 +++ .../Propositional/CNF/ExactOne/PolyTime.lean | 247 +++++++ .../Propositional/CNF/ExactOne/Positive.lean | 633 ++++++++++++++++++ ComplexityTests/PositiveOneInThreeSAT.lean | 119 ++++ 13 files changed, 3005 insertions(+) create mode 100644 Complexity/Computability/Complexity/Problems/OneInThreeSAT.lean create mode 100644 Complexity/Computability/Complexity/Problems/OneInThreeSAT/Reduction.lean create mode 100644 Complexity/Logics/Propositional/CNF/ExactOne.lean create mode 100644 Complexity/Logics/Propositional/CNF/ExactOne/BlockBounds.lean create mode 100644 Complexity/Logics/Propositional/CNF/ExactOne/Blocks.lean create mode 100644 Complexity/Logics/Propositional/CNF/ExactOne/Compile.lean create mode 100644 Complexity/Logics/Propositional/CNF/ExactOne/FromSAT.lean create mode 100644 Complexity/Logics/Propositional/CNF/ExactOne/Gadgets.lean create mode 100644 Complexity/Logics/Propositional/CNF/ExactOne/ModelCount.lean create mode 100644 Complexity/Logics/Propositional/CNF/ExactOne/PolyTime.lean create mode 100644 Complexity/Logics/Propositional/CNF/ExactOne/Positive.lean create mode 100644 ComplexityTests/PositiveOneInThreeSAT.lean diff --git a/Complexity.lean b/Complexity.lean index a721be0d6..b0a122b46 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -807,6 +807,8 @@ public import Complexity.Computability.Complexity.Problems.IntegerProgramming public import Complexity.Computability.Complexity.Problems.Knapsack public import Complexity.Computability.Complexity.Problems.MonotoneCircuitValue public import Complexity.Computability.Complexity.Problems.MonotoneCircuitValue.Completeness +public import Complexity.Computability.Complexity.Problems.OneInThreeSAT +public import Complexity.Computability.Complexity.Problems.OneInThreeSAT.Reduction public import Complexity.Computability.Complexity.Problems.Partition public import Complexity.Computability.Complexity.Problems.PlanarThreeSATThree public import Complexity.Computability.Complexity.Problems.SAT @@ -2142,6 +2144,15 @@ 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.ExactOne +public import Complexity.Logics.Propositional.CNF.ExactOne.BlockBounds +public import Complexity.Logics.Propositional.CNF.ExactOne.Blocks +public import Complexity.Logics.Propositional.CNF.ExactOne.Compile +public import Complexity.Logics.Propositional.CNF.ExactOne.FromSAT +public import Complexity.Logics.Propositional.CNF.ExactOne.Gadgets +public import Complexity.Logics.Propositional.CNF.ExactOne.ModelCount +public import Complexity.Logics.Propositional.CNF.ExactOne.PolyTime +public import Complexity.Logics.Propositional.CNF.ExactOne.Positive public import Complexity.Logics.Propositional.CNF.ExactlyOne public import Complexity.Logics.Propositional.CNF.Gadgets public import Complexity.Logics.Propositional.CNF.ImplicationChain diff --git a/Complexity/Computability/Complexity/Problems/OneInThreeSAT.lean b/Complexity/Computability/Complexity/Problems/OneInThreeSAT.lean new file mode 100644 index 000000000..136ac2b76 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/OneInThreeSAT.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.Logics.Propositional.CNF.ExactOne.ModelCount +public import Complexity.Computability.Complexity.Problems.ThreeSATThree.Syntax +import Complexity.Computability.Complexity.Problems.SAT.PolyTime +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.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain + +/-! +# Positive exact-one satisfiability + +All four positive languages prohibit negated literals. `Three` bounds clause length by three; +`ExactThree` requires three distinct variables in each clause. The final `Three` bounds total +literal occurrences per variable by three, including repeated positions. + +These notions reuse the signed CNF representation and the occurrence-count semantics +`CNF.evalOne`. Certificates use the same first-occurrence assignment as ordinary SAT. + +## Main definitions + +* `PositiveOneInThreeSAT`, `PositiveOneInExactThreeSAT`. +* `PositiveOneInThreeSATThree`, `PositiveOneInExactThreeSATThree`. +* `OneSAT`: exact-one satisfiability without syntax restrictions. + +## Main results + +* `OneSAT.mem_NP`: unrestricted exact-one satisfiability belongs to NP. +* Each positive language has an analogous `mem_NP` theorem. + +## References + +* [Hunt, Marathe, Radhakrishnan and Stearns][HuntMaratheRadhakrishnanStearns1998], + Definition 2.9 and Theorem 3.8. +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Cslib.Logic.PL Turing.MultiTapeTM + +attribute [local fun_prop] ComputableInPolyTime.tidy + +/-- Exact-one satisfiable formulas, without a clause-size or polarity restriction. -/ +def OneSAT : Set (CNF ℕ) := {φ | φ.OneSatisfiable} + +namespace OneSAT + +/-- Evaluate exact-one clauses under the usual first-occurrence certificate assignment. -/ +def verifier (x : CNF ℕ × List Bool) : Bool := x.1.evalOne (SAT.assignment x.1 x.2) + +/-- The standard SAT certificates also represent every exact-one satisfying assignment. -/ +theorem isVerifier : IsVerifier encCNF Polynomial.X verifier OneSAT := by + refine IsVerifier.of_padding (fun φ w _ h => ⟨_, h⟩) fun φ ⟨τ, hτ⟩ => ?_ + refine ⟨φ.vars.map τ, by simpa using SAT.length_vars_le φ, fun k => ?_⟩ + refine (CNF.evalOne_congr fun v hv => ?_).trans hτ + have hlt : φ.vars.idxOf v < φ.vars.length := List.idxOf_lt_length_iff.mpr hv + have hlt' : φ.vars.idxOf v < (φ.vars.map τ).length := by simpa using hlt + rw [SAT.assignment, List.getD_eq_getElem?_getD, List.getElem?_append_left hlt', + List.getElem?_map, List.getElem?_eq_getElem hlt, List.getElem_idxOf hlt] + rfl + +/-- Count the true literal occurrences of a clause in native polynomial time. -/ +@[fun_prop] +theorem computableInPolyTime_clause_count : + ComputableInPolyTime + (fun p : (CNF ℕ × List Bool) × Clause ℕ => + p.2.countP (Literal.eval (SAT.assignment p.1.1 p.1.2))) + (encPair (encPair encCNF encBits) (encList (encPair encNat encBool))) encUnary := by + refine .of_tidy ?_ + apply ComputableTidilyInPolyTime.countPWithInput + · fun_prop + · fun_prop [Literal.eval, SAT.assignment] + +/-- An exact-one clause is verified in polynomial time. -/ +@[fun_prop] +theorem computableInPolyTime_clause : + ComputableInPolyTime + (fun p : (CNF ℕ × List Bool) × Clause ℕ => + p.2.evalOne (SAT.assignment p.1.1 p.1.2)) + (encPair (encPair encCNF encBits) (encList (encPair encNat encBool))) encBool := by + refine .of_tidy ?_ + unfold Clause.evalOne + exact ComputableTidilyInPolyTime.unary_beq computableInPolyTime_clause_count.tidy + (ComputableTidilyInPolyTime.const 1) + +/-- The exact-one verifier is polynomial-time for binary formula labels. -/ +@[fun_prop] +theorem computableInPolyTime_verifier : + ComputableInPolyTime verifier (encPair encCNF encBits) encBool := by + have hclause := computableInPolyTime_clause.tidy + refine .of_tidy ?_ + fun_prop [verifier, CNF.evalOne] + +/-- Exact-one satisfiability belongs to NP. -/ +theorem mem_NP : OneSAT ∈ NP encCNF := + ⟨Polynomial.X, verifier, computableInPolyTime_verifier, isVerifier⟩ + +/-- Check that all literal polarities are positive. -/ +def positiveCheck (φ : CNF ℕ) : Bool := φ.all fun c => c.all Prod.snd + +/-- Positivity means that every occurring literal has polarity `true`. -/ +theorem positiveCheck_iff (φ : CNF ℕ) : + positiveCheck φ = true ↔ ∀ c ∈ φ, ∀ l ∈ c, l.2 = true := by + simp [positiveCheck] + +/-- Check for three distinct variable positions without any restriction on the input length. -/ +def exactClauseCheck (c : Clause ℕ) : Bool := + (c.length == 3) && + (!((c.map Prod.fst).getD 0 0 == (c.map Prod.fst).getD 1 0)) && + (!((c.map Prod.fst).getD 0 0 == (c.map Prod.fst).getD 2 0)) && + (!((c.map Prod.fst).getD 1 0 == (c.map Prod.fst).getD 2 0)) + +/-- The exact-clause check requires three pairwise distinct variables. -/ +theorem exactClauseCheck_iff (c : Clause ℕ) : + exactClauseCheck c = true ↔ c.length = 3 ∧ (c.map Prod.fst).Nodup := by + rcases c with _ | ⟨a, _ | ⟨b, _ | ⟨d, _ | ⟨e, t⟩⟩⟩⟩ + all_goals simp [exactClauseCheck, List.getD, List.nodup_cons, and_assoc, and_left_comm, + and_comm] + +/-- Positivity is decidable in native polynomial time. -/ +@[fun_prop] +theorem computableInPolyTime_positiveCheck : + ComputableInPolyTime positiveCheck encCNF encBool := by + refine .of_tidy ?_ + unfold positiveCheck encCNF + fun_prop + +/-- Exact length and distinctness of a clause are decidable in native polynomial time. -/ +@[fun_prop] +theorem computableInPolyTime_exactClauseCheck : + ComputableInPolyTime exactClauseCheck (encList (encPair encNat encBool)) encBool := by + refine .of_tidy ?_ + have hvars : ComputableTidilyInPolyTime (fun c : Clause ℕ => c.map Prod.fst) + (encList (encPair encNat encBool)) (encList encNat) := by fun_prop + have hread (i : ℕ) : ComputableTidilyInPolyTime + (fun c : Clause ℕ => (c.map Prod.fst).getD i 0) + (encList (encPair encNat encBool)) encNat := + (ComputableTidilyInPolyTime.listGetD (encA := encNat) (encL := encList encNat) 0).comp + (hvars.prod (ComputableTidilyInPolyTime.const i)) + have hlen : ComputableTidilyInPolyTime (fun c : Clause ℕ => c.length == 3) + (encList (encPair encNat encBool)) encBool := + ComputableTidilyInPolyTime.unary_beq + (ComputableTidilyInPolyTime.length (encPair encNat encBool)) + (ComputableTidilyInPolyTime.const 3) + have h01 := (hread 0).beq (hread 1) + have h02 := (hread 0).beq (hread 2) + have h12 := (hread 1).beq (hread 2) + unfold exactClauseCheck + fun_prop + +end OneSAT + +/-- Positive exact-one satisfiability with at most three literals per clause. -/ +def PositiveOneInThreeSAT : Set (CNF ℕ) := + {φ | (∀ c ∈ φ, c.length ≤ 3) ∧ OneSAT.positiveCheck φ = true ∧ φ.OneSatisfiable} + +/-- Positive exact-one satisfiability with exactly three distinct variables per clause. -/ +def PositiveOneInExactThreeSAT : Set (CNF ℕ) := + {φ | (∀ c ∈ φ, c.length = 3 ∧ (c.map Prod.fst).Nodup) ∧ + OneSAT.positiveCheck φ = true ∧ φ.OneSatisfiable} + +/-- Positive width-three exact-one satisfiability with at most three occurrences per variable. -/ +def PositiveOneInThreeSATThree : Set (CNF ℕ) := + {φ | φ ∈ PositiveOneInThreeSAT ∧ ∀ v, φ.vars.count v ≤ 3} + +/-- Positive exact-three exact-one satisfiability with at most three occurrences per variable. -/ +def PositiveOneInExactThreeSATThree : Set (CNF ℕ) := + {φ | φ ∈ PositiveOneInExactThreeSAT ∧ ∀ v, φ.vars.count v ≤ 3} + +namespace PositiveOneInThreeSAT + +/-- The positive width-three syntax guard. -/ +def syntaxCheck (φ : CNF ℕ) : Bool := + (φ.all fun c => decide (c.length ≤ 3)) && OneSAT.positiveCheck φ + +/-- The syntax guard checks exactly the nonsemantic restrictions. -/ +theorem syntaxCheck_iff (φ : CNF ℕ) : + syntaxCheck φ = true ↔ (∀ c ∈ φ, c.length ≤ 3) ∧ OneSAT.positiveCheck φ = true := by + simp [syntaxCheck] + +/-- The positive width-three syntax guard is polynomial-time. -/ +@[fun_prop] +theorem computableInPolyTime_syntaxCheck : + ComputableInPolyTime syntaxCheck encCNF encBool := by + have hp := OneSAT.computableInPolyTime_positiveCheck.tidy + refine .of_tidy ?_ + fun_prop [syntaxCheck, encCNF] + +/-- Positive width-three exact-one satisfiability belongs to NP. -/ +theorem mem_NP : PositiveOneInThreeSAT ∈ NP encCNF := by + have h : PositiveOneInThreeSAT = {φ | syntaxCheck φ = true ∧ φ ∈ OneSAT} := by + ext φ + simp [PositiveOneInThreeSAT, syntaxCheck_iff, OneSAT, and_assoc] + rw [h] + exact ⟨Polynomial.X, fun x => syntaxCheck x.1 && OneSAT.verifier x, + by fun_prop, OneSAT.isVerifier.guard⟩ + +end PositiveOneInThreeSAT + +namespace PositiveOneInExactThreeSAT + +/-- The positive exact-three syntax guard. -/ +def syntaxCheck (φ : CNF ℕ) : Bool := + (φ.all OneSAT.exactClauseCheck) && OneSAT.positiveCheck φ + +/-- The syntax guard checks three distinct variables and positivity. -/ +theorem syntaxCheck_iff (φ : CNF ℕ) : + syntaxCheck φ = true ↔ + (∀ c ∈ φ, c.length = 3 ∧ (c.map Prod.fst).Nodup) ∧ OneSAT.positiveCheck φ = true := by + simp [syntaxCheck, OneSAT.exactClauseCheck_iff] + +/-- The positive exact-three syntax guard is polynomial-time. -/ +@[fun_prop] +theorem computableInPolyTime_syntaxCheck : + ComputableInPolyTime syntaxCheck encCNF encBool := by + have hp := OneSAT.computableInPolyTime_positiveCheck.tidy + have he := OneSAT.computableInPolyTime_exactClauseCheck.tidy + refine .of_tidy ?_ + fun_prop [syntaxCheck, encCNF] + +/-- Positive exact-three exact-one satisfiability belongs to NP. -/ +theorem mem_NP : PositiveOneInExactThreeSAT ∈ NP encCNF := by + have h : PositiveOneInExactThreeSAT = {φ | syntaxCheck φ = true ∧ φ ∈ OneSAT} := by + ext φ + simp [PositiveOneInExactThreeSAT, syntaxCheck_iff, OneSAT, and_assoc] + rw [h] + exact ⟨Polynomial.X, fun x => syntaxCheck x.1 && OneSAT.verifier x, + by fun_prop, OneSAT.isVerifier.guard⟩ + +end PositiveOneInExactThreeSAT + +namespace PositiveOneInThreeSATThree + +/-- Check positivity, width three, and at most three occurrences per variable. -/ +def syntaxCheck (φ : CNF ℕ) : Bool := + ThreeSATThree.syntaxCheck φ && OneSAT.positiveCheck φ + +/-- The bounded positive syntax guard is polynomial-time. -/ +@[fun_prop] +theorem computableInPolyTime_syntaxCheck : + ComputableInPolyTime syntaxCheck encCNF encBool := by + unfold syntaxCheck + fun_prop + +/-- The bounded positive syntax guard checks exactly the syntactic restrictions. -/ +theorem syntaxCheck_iff (φ : CNF ℕ) : + syntaxCheck φ = true ↔ (∀ c ∈ φ, c.length ≤ 3) ∧ + OneSAT.positiveCheck φ = true ∧ ∀ v, φ.vars.count v ≤ 3 := by + simp [syntaxCheck, ThreeSATThree.syntaxCheck_iff, and_assoc, and_comm] + +/-- Positive width-three exact-one satisfiability with occurrence bound three belongs to NP. -/ +theorem mem_NP : PositiveOneInThreeSATThree ∈ NP encCNF := by + have h : PositiveOneInThreeSATThree = {φ | syntaxCheck φ = true ∧ φ ∈ OneSAT} := by + ext φ + simp [PositiveOneInThreeSATThree, PositiveOneInThreeSAT, syntaxCheck_iff, OneSAT, + and_assoc, and_comm, and_left_comm] + rw [h] + exact ⟨Polynomial.X, fun x => syntaxCheck x.1 && OneSAT.verifier x, + by fun_prop, OneSAT.isVerifier.guard⟩ + +end PositiveOneInThreeSATThree + +namespace PositiveOneInExactThreeSATThree + +/-- Check positive exact-three clauses and the occurrence bound three. -/ +def syntaxCheck (φ : CNF ℕ) : Bool := + PositiveOneInExactThreeSAT.syntaxCheck φ && ThreeSATThree.syntaxCheck φ + +/-- The bounded positive exact-three syntax guard is polynomial-time. -/ +@[fun_prop] +theorem computableInPolyTime_syntaxCheck : + ComputableInPolyTime syntaxCheck encCNF encBool := by + unfold syntaxCheck + fun_prop + +/-- The bounded exact-three guard checks exactly the syntactic restrictions. -/ +theorem syntaxCheck_iff (φ : CNF ℕ) : + syntaxCheck φ = true ↔ + (∀ c ∈ φ, c.length = 3 ∧ (c.map Prod.fst).Nodup) ∧ + OneSAT.positiveCheck φ = true ∧ ∀ v, φ.vars.count v ≤ 3 := by + rw [syntaxCheck, Bool.and_eq_true, PositiveOneInExactThreeSAT.syntaxCheck_iff, + ThreeSATThree.syntaxCheck_iff] + constructor + · rintro ⟨⟨hc, hp⟩, _, hv⟩ + exact ⟨hc, hp, hv⟩ + · rintro ⟨hc, hp, hv⟩ + exact ⟨⟨hc, hp⟩, (fun c h => (hc c h).1.le), hv⟩ + +/-- Positive exact-three exact-one satisfiability with occurrence bound three belongs to NP. -/ +theorem mem_NP : PositiveOneInExactThreeSATThree ∈ NP encCNF := by + have h : PositiveOneInExactThreeSATThree = {φ | syntaxCheck φ = true ∧ φ ∈ OneSAT} := by + ext φ + simp [PositiveOneInExactThreeSATThree, PositiveOneInExactThreeSAT, syntaxCheck_iff, + OneSAT, and_assoc, and_comm, and_left_comm] + rw [h] + exact ⟨Polynomial.X, fun x => syntaxCheck x.1 && OneSAT.verifier x, + by fun_prop, OneSAT.isVerifier.guard⟩ + +end PositiveOneInExactThreeSATThree + +/-- Count exact-one models on the occurring variables, without a syntax restriction. -/ +def SharpOneSAT (φ : CNF ℕ) : ℕ := φ.oneModelCount + +/-- Count positive width-three exact-one models, returning zero on other syntax. -/ +def SharpPositiveOneInThreeSAT (φ : CNF ℕ) : ℕ := + if PositiveOneInThreeSAT.syntaxCheck φ then SharpOneSAT φ else 0 + +/-- Count positive exact-three exact-one models, returning zero on other syntax. -/ +def SharpPositiveOneInExactThreeSAT (φ : CNF ℕ) : ℕ := + if PositiveOneInExactThreeSAT.syntaxCheck φ then SharpOneSAT φ else 0 + +/-- Count positive width-three, occurrence-three exact-one models; reject other syntax. -/ +def SharpPositiveOneInThreeSATThree (φ : CNF ℕ) : ℕ := + if PositiveOneInThreeSATThree.syntaxCheck φ then SharpOneSAT φ else 0 + +/-- Count positive exact-three, occurrence-three exact-one models; reject other syntax. -/ +def SharpPositiveOneInExactThreeSATThree (φ : CNF ℕ) : ℕ := + if PositiveOneInExactThreeSATThree.syntaxCheck φ then SharpOneSAT φ else 0 + +/-- Exact-one satisfiability is equivalent to a positive exact-one model count. -/ +theorem SharpOneSAT.mem_OneSAT_iff {φ : CNF ℕ} : φ ∈ OneSAT ↔ 0 < SharpOneSAT φ := by + simp [OneSAT, SharpOneSAT, CNF.oneModelCount_pos_iff] + +/-- Membership in the positive width-three language is positivity of its guarded count. -/ +theorem SharpPositiveOneInThreeSAT.mem_PositiveOneInThreeSAT_iff {φ : CNF ℕ} : + φ ∈ PositiveOneInThreeSAT ↔ 0 < SharpPositiveOneInThreeSAT φ := by + have h : φ ∈ PositiveOneInThreeSAT ↔ + PositiveOneInThreeSAT.syntaxCheck φ = true ∧ φ.OneSatisfiable := by + simp [PositiveOneInThreeSAT, PositiveOneInThreeSAT.syntaxCheck_iff, and_assoc] + rw [h] + unfold SharpPositiveOneInThreeSAT + split <;> simp_all [SharpOneSAT, CNF.oneModelCount_pos_iff] + +/-- Membership in the positive exact-three language is positivity of its guarded count. -/ +theorem SharpPositiveOneInExactThreeSAT.mem_PositiveOneInExactThreeSAT_iff {φ : CNF ℕ} : + φ ∈ PositiveOneInExactThreeSAT ↔ 0 < SharpPositiveOneInExactThreeSAT φ := by + have h : φ ∈ PositiveOneInExactThreeSAT ↔ + PositiveOneInExactThreeSAT.syntaxCheck φ = true ∧ φ.OneSatisfiable := by + simp [PositiveOneInExactThreeSAT, PositiveOneInExactThreeSAT.syntaxCheck_iff, and_assoc] + rw [h] + unfold SharpPositiveOneInExactThreeSAT + split <;> simp_all [SharpOneSAT, CNF.oneModelCount_pos_iff] + +/-- Membership in the positive width-three, occurrence-three language is a positive count. -/ +theorem SharpPositiveOneInThreeSATThree.mem_PositiveOneInThreeSATThree_iff {φ : CNF ℕ} : + φ ∈ PositiveOneInThreeSATThree ↔ 0 < SharpPositiveOneInThreeSATThree φ := by + have h : φ ∈ PositiveOneInThreeSATThree ↔ + PositiveOneInThreeSATThree.syntaxCheck φ = true ∧ φ.OneSatisfiable := by + simp [PositiveOneInThreeSATThree, PositiveOneInThreeSAT, + PositiveOneInThreeSATThree.syntaxCheck_iff, and_assoc, and_comm, and_left_comm] + rw [h] + unfold SharpPositiveOneInThreeSATThree + split <;> simp_all [SharpOneSAT, CNF.oneModelCount_pos_iff] + +/-- Membership in the positive exact-three, occurrence-three language is a positive count. -/ +theorem SharpPositiveOneInExactThreeSATThree.mem_PositiveOneInExactThreeSATThree_iff + {φ : CNF ℕ} : + φ ∈ PositiveOneInExactThreeSATThree ↔ 0 < SharpPositiveOneInExactThreeSATThree φ := by + have h : φ ∈ PositiveOneInExactThreeSATThree ↔ + PositiveOneInExactThreeSATThree.syntaxCheck φ = true ∧ φ.OneSatisfiable := by + simp [PositiveOneInExactThreeSATThree, PositiveOneInExactThreeSAT, + PositiveOneInExactThreeSATThree.syntaxCheck_iff, and_assoc, and_comm, and_left_comm] + rw [h] + unfold SharpPositiveOneInExactThreeSATThree + split <;> simp_all [SharpOneSAT, CNF.oneModelCount_pos_iff] + +end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/Problems/OneInThreeSAT/Reduction.lean b/Complexity/Computability/Complexity/Problems/OneInThreeSAT/Reduction.lean new file mode 100644 index 000000000..fbbd71353 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/OneInThreeSAT/Reduction.lean @@ -0,0 +1,214 @@ +/- +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.OneInThreeSAT +public import Complexity.Computability.Complexity.Problems.ThreeSATThree.Size +public import Complexity.Computability.Complexity.Problems.ThreeSATThree.Counting +public import Complexity.Logics.Propositional.CNF.ExactOne.Compile +import Complexity.Logics.Propositional.CNF.ExactOne.PolyTime +import Mathlib.Tactic.Linarith + +/-! +# Parsimonious constant-factor reductions to positive exact-one SAT + +Ordinary occurrence splitting is followed by the two constant-size local replacements of +[Hunt, Marathe, Radhakrishnan and Stearns, Theorem 3.8] +[HuntMaratheRadhakrishnanStearns1998]. The output has three distinct positive variables per +clause and at most three occurrences of every variable. Thus one compiler proves hardness of +all four positive variants, with no negated literals in any target. + +The new local compiler contributes at most 169 clauses and 507 literal occurrences per input +clause. Relative to the original width-three input, the composed bounds are 169 and 507 times +its clause-plus-literal count. These measure actual lists, independently of binary label sizes. + +## Main definitions + +* `toPositiveOneInExactThreeSATThree`: total reduction from ordinary 3SAT. + +## Main results + +* `oneModelCount_toPositiveOneInExactThreeSATThree`: exact preservation of model counts. +* `PositiveOneInExactThreeSATThree_npComplete`: the strongest nonplanar endpoint. +* `PositiveOneInThreeSAT_npComplete`, `PositiveOneInExactThreeSAT_npComplete`, + `PositiveOneInThreeSATThree_npComplete`: the other positive variants. + +## References + +* [Hunt, Marathe, Radhakrishnan and Stearns][HuntMaratheRadhakrishnanStearns1998], + Theorem 3.8, Figures 3.4–3.5 and Lemma 3.6. + +The composition, occurrence accounting, and quantitative bounds are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Cslib.Logic.PL Turing.MultiTapeTM + +/-- Split ordinary occurrences, then apply the parsimonious positive exact-three compiler. -/ +def toPositiveOneInExactThreeSATThree (φ : CNF ℕ) : CNF ℕ := + CNF.PositiveExactOneCompile.formula (toThreeSATThree φ) + +/-- The complete compiler is native polynomial-time under ordinary binary formula encodings. -/ +@[fun_prop] +theorem computableInPolyTime_toPositiveOneInExactThreeSATThree : + ComputableInPolyTime toPositiveOneInExactThreeSATThree encCNF encCNF := + (CNF.PositiveExactOneCompile.computableInPolyTime_formula encNat).comp + computableInPolyTime_toThreeSATThree + +/-- All output clauses have three distinct variables. -/ +theorem exactClauses_toPositiveOneInExactThreeSATThree (φ : CNF ℕ) : + ∀ c ∈ toPositiveOneInExactThreeSATThree φ, + c.length = 3 ∧ (c.map Prod.fst).Nodup := + fun c hc => ⟨CNF.PositiveExactOneCompile.width_formula _ c hc, + CNF.PositiveExactOneCompile.nodup_formula _ c hc⟩ + +/-- Every emitted target literal is positive. -/ +theorem positive_toPositiveOneInExactThreeSATThree (φ : CNF ℕ) : + OneSAT.positiveCheck (toPositiveOneInExactThreeSATThree φ) = true := + (OneSAT.positiveCheck_iff _).mpr (CNF.PositiveExactOneCompile.positive_formula _) + +/-- Every output variable occurs at most three times. -/ +theorem count_vars_toPositiveOneInExactThreeSATThree_le (φ : CNF ℕ) : + ∀ v, (toPositiveOneInExactThreeSATThree φ).vars.count v ≤ 3 := + CNF.PositiveExactOneCompile.count_vars_formula_le _ (width_toThreeSATThree φ) + (count_vars_toThreeSATThree_le φ) + +/-- The actual exact-one model count is the original ordinary model count. -/ +theorem oneModelCount_toPositiveOneInExactThreeSATThree (φ : CNF ℕ) + (hw : ∀ c ∈ φ, c.length ≤ 3) : + (toPositiveOneInExactThreeSATThree φ).oneModelCount = φ.modelCount := + (CNF.PositiveExactOneCompile.oneModelCount_formula _ (width_toThreeSATThree φ)).trans + (modelCount_toThreeSATThree φ hw) + +/-- Total correctness, including rejected malformed ordinary inputs. -/ +theorem oneSatisfiable_toPositiveOneInExactThreeSATThree_iff (φ : CNF ℕ) : + (toPositiveOneInExactThreeSATThree φ).OneSatisfiable ↔ φ ∈ ThreeSAT := by + rw [toPositiveOneInExactThreeSATThree, + CNF.PositiveExactOneCompile.oneSatisfiable_formula _ (width_toThreeSATThree φ)] + have he : (toThreeSATThree φ).Satisfiable ↔ toThreeSATThree φ ∈ ThreeSATThree := + ⟨fun hs => ⟨width_toThreeSATThree φ, count_vars_toThreeSATThree_le φ, hs⟩, + fun hs => hs.2.2⟩ + exact he.trans (mem_ThreeSATThree_toThreeSATThree_iff φ) + +/-- Explicit constant-factor formula bounds relative to the original valid 3SAT instance. -/ +theorem size_toPositiveOneInExactThreeSATThree (φ : CNF ℕ) + (hw : ∀ c ∈ φ, c.length ≤ 3) : + (toPositiveOneInExactThreeSATThree φ).length ≤ 169 * (φ.length + φ.vars.length) ∧ + (toPositiveOneInExactThreeSATThree φ).vars.length ≤ + 507 * (φ.length + φ.vars.length) := by + have hc := CNF.PositiveExactOneCompile.length_formula_le (toThreeSATThree φ) + have hl := CNF.PositiveExactOneCompile.length_vars_formula_le (toThreeSATThree φ) + rw [length_toThreeSATThree φ hw] at hc hl + exact ⟨hc, hl⟩ + +/-- The guarded counting functions agree on every input, including invalid syntax. -/ +theorem sharpPositiveOneInExactThreeSATThree_toPositiveOneInExactThreeSATThree (φ : CNF ℕ) : + SharpPositiveOneInExactThreeSATThree (toPositiveOneInExactThreeSATThree φ) = + SharpThreeSAT φ := by + have hg := (PositiveOneInExactThreeSATThree.syntaxCheck_iff _).mpr + ⟨exactClauses_toPositiveOneInExactThreeSATThree φ, + positive_toPositiveOneInExactThreeSATThree φ, + count_vars_toPositiveOneInExactThreeSATThree_le φ⟩ + have hs := (ThreeSATThree.syntaxCheck_iff _).mpr + ⟨width_toThreeSATThree φ, count_vars_toThreeSATThree_le φ⟩ + simp only [SharpPositiveOneInExactThreeSATThree, hg, ↓reduceIte] + change (toPositiveOneInExactThreeSATThree φ).oneModelCount = _ + rw [toPositiveOneInExactThreeSATThree, + CNF.PositiveExactOneCompile.oneModelCount_formula _ (width_toThreeSATThree φ)] + simpa only [SharpThreeSATThree, hs, ↓reduceIte, SharpSAT] using + sharpThreeSATThree_toThreeSATThree φ + +/-- The native compiler is a parsimonious counting reduction on all encoded inputs. -/ +theorem parsimoniousReducible_SharpThreeSAT_SharpPositiveOneInExactThreeSATThree : + ParsimoniousReducible encCNF encCNF SharpThreeSAT SharpPositiveOneInExactThreeSATThree := + ⟨toPositiveOneInExactThreeSATThree, computableInPolyTime_toPositiveOneInExactThreeSATThree, + fun φ => (sharpPositiveOneInExactThreeSATThree_toPositiveOneInExactThreeSATThree φ).symm⟩ + +/-- The strongest target's membership equivalence. -/ +theorem mem_PositiveOneInExactThreeSATThree_toPositiveOneInExactThreeSATThree_iff + (φ : CNF ℕ) : + toPositiveOneInExactThreeSATThree φ ∈ PositiveOneInExactThreeSATThree ↔ φ ∈ ThreeSAT := by + change (_ ∧ _ ∧ _) ∧ _ ↔ _ + rw [and_iff_right (exactClauses_toPositiveOneInExactThreeSATThree φ), + and_iff_right (positive_toPositiveOneInExactThreeSATThree φ), + and_iff_left (count_vars_toPositiveOneInExactThreeSATThree_le φ)] + exact oneSatisfiable_toPositiveOneInExactThreeSATThree_iff φ + +/-- The same output proves the unrestricted exact-three target's membership equivalence. -/ +theorem mem_PositiveOneInExactThreeSAT_toPositiveOneInExactThreeSATThree_iff (φ : CNF ℕ) : + toPositiveOneInExactThreeSATThree φ ∈ PositiveOneInExactThreeSAT ↔ φ ∈ ThreeSAT := by + change _ ∧ _ ∧ _ ↔ _ + rw [and_iff_right (exactClauses_toPositiveOneInExactThreeSATThree φ), + and_iff_right (positive_toPositiveOneInExactThreeSATThree φ)] + exact oneSatisfiable_toPositiveOneInExactThreeSATThree_iff φ + +/-- The same output proves positive width-three satisfiability's membership equivalence. -/ +theorem mem_PositiveOneInThreeSAT_toPositiveOneInExactThreeSATThree_iff (φ : CNF ℕ) : + toPositiveOneInExactThreeSATThree φ ∈ PositiveOneInThreeSAT ↔ φ ∈ ThreeSAT := by + change _ ∧ _ ∧ _ ↔ _ + rw [and_iff_right (show ∀ c ∈ toPositiveOneInExactThreeSATThree φ, c.length ≤ 3 from + fun c hc => (exactClauses_toPositiveOneInExactThreeSATThree φ c hc).1.le), + and_iff_right (positive_toPositiveOneInExactThreeSATThree φ)] + exact oneSatisfiable_toPositiveOneInExactThreeSATThree_iff φ + +/-- The same output proves the bounded-occurrence width-three target's equivalence. -/ +theorem mem_PositiveOneInThreeSATThree_toPositiveOneInExactThreeSATThree_iff (φ : CNF ℕ) : + toPositiveOneInExactThreeSATThree φ ∈ PositiveOneInThreeSATThree ↔ φ ∈ ThreeSAT := by + change _ ∧ _ ↔ _ + rw [and_iff_left (count_vars_toPositiveOneInExactThreeSATThree_le φ)] + exact mem_PositiveOneInThreeSAT_toPositiveOneInExactThreeSATThree_iff φ + +/-- Ordinary 3SAT reduces to positive exact-three, occurrence-three exact-one SAT. -/ +theorem polyTimeReducible_ThreeSAT_PositiveOneInExactThreeSATThree : + PolyTimeReducible encCNF encCNF ThreeSAT PositiveOneInExactThreeSATThree := + ⟨toPositiveOneInExactThreeSATThree, computableInPolyTime_toPositiveOneInExactThreeSATThree, + fun φ => (mem_PositiveOneInExactThreeSATThree_toPositiveOneInExactThreeSATThree_iff φ).symm⟩ + +/-- Positive exact-three, occurrence-three exact-one SAT is NP-hard. -/ +theorem PositiveOneInExactThreeSATThree_npHard : NPHard encCNF PositiveOneInExactThreeSATThree := + ThreeSAT_npHard.trans polyTimeReducible_ThreeSAT_PositiveOneInExactThreeSATThree + +/-- Positive exact-three, occurrence-three exact-one SAT is NP-complete. -/ +theorem PositiveOneInExactThreeSATThree_npComplete : + NPComplete encCNF PositiveOneInExactThreeSATThree := + ⟨PositiveOneInExactThreeSATThree.mem_NP, PositiveOneInExactThreeSATThree_npHard⟩ + +/-- Positive exact-three exact-one SAT is NP-hard. -/ +theorem PositiveOneInExactThreeSAT_npHard : NPHard encCNF PositiveOneInExactThreeSAT := + ThreeSAT_npHard.trans ⟨toPositiveOneInExactThreeSATThree, + computableInPolyTime_toPositiveOneInExactThreeSATThree, + fun φ => (mem_PositiveOneInExactThreeSAT_toPositiveOneInExactThreeSATThree_iff φ).symm⟩ + +/-- Positive exact-three exact-one SAT is NP-complete. -/ +theorem PositiveOneInExactThreeSAT_npComplete : NPComplete encCNF PositiveOneInExactThreeSAT := + ⟨PositiveOneInExactThreeSAT.mem_NP, PositiveOneInExactThreeSAT_npHard⟩ + +/-- Positive width-three exact-one SAT is NP-hard. -/ +theorem PositiveOneInThreeSAT_npHard : NPHard encCNF PositiveOneInThreeSAT := + ThreeSAT_npHard.trans ⟨toPositiveOneInExactThreeSATThree, + computableInPolyTime_toPositiveOneInExactThreeSATThree, + fun φ => (mem_PositiveOneInThreeSAT_toPositiveOneInExactThreeSATThree_iff φ).symm⟩ + +/-- Positive width-three exact-one SAT is NP-complete. -/ +theorem PositiveOneInThreeSAT_npComplete : NPComplete encCNF PositiveOneInThreeSAT := + ⟨PositiveOneInThreeSAT.mem_NP, PositiveOneInThreeSAT_npHard⟩ + +/-- Positive width-three, occurrence-three exact-one SAT is NP-hard. -/ +theorem PositiveOneInThreeSATThree_npHard : NPHard encCNF PositiveOneInThreeSATThree := + ThreeSAT_npHard.trans ⟨toPositiveOneInExactThreeSATThree, + computableInPolyTime_toPositiveOneInExactThreeSATThree, + fun φ => (mem_PositiveOneInThreeSATThree_toPositiveOneInExactThreeSATThree_iff φ).symm⟩ + +/-- Positive width-three, occurrence-three exact-one SAT is NP-complete. -/ +theorem PositiveOneInThreeSATThree_npComplete : NPComplete encCNF PositiveOneInThreeSATThree := + ⟨PositiveOneInThreeSATThree.mem_NP, PositiveOneInThreeSATThree_npHard⟩ + +end Cslib.Complexity diff --git a/Complexity/Logics/Propositional/CNF/ExactOne.lean b/Complexity/Logics/Propositional/CNF/ExactOne.lean new file mode 100644 index 000000000..6a866ae79 --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/ExactOne.lean @@ -0,0 +1,231 @@ +/- +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 +import Mathlib.Data.List.Count + +/-! +# Exact-one semantics for clauses + +`Clause.evalOne` counts true literal occurrences, including repeated occurrences of the same +variable. `CNF.evalOne` requires exactly one true literal in every clause. This reuses the +existing signed CNF representation; positivity and clause-size restrictions are separate. + +`CNF.oneEncoding` expresses these semantics with ordinary CNF clauses and no new variables. +Unlike `CNF.exactlyOne`, which constrains distinct variables, this encoding counts positions. + +## Main definitions + +* `Clause.evalOne`, `CNF.evalOne`, `CNF.OneSatisfiable`: exact-one semantics. +* `Clause.oneEncoding`, `CNF.oneEncoding`: the ordinary CNF encoding. + +## Main results + +* `CNF.eval_oneEncoding`: correctness of the encoding under the same assignment. +* `CNF.mem_vars_oneEncoding`: the encoding has exactly the source variables. + +## References + +* [Hunt, Marathe, Radhakrishnan and Stearns][HuntMaratheRadhakrishnanStearns1998], + Definition 2.9. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL + +variable {V W : Type*} + +/-- Exactly one literal occurrence of the clause is true. -/ +def Clause.evalOne (σ : V → Bool) (c : Clause V) : Bool := + c.countP (Literal.eval σ) == 1 + +/-- Every clause has exactly one true literal occurrence. -/ +def CNF.evalOne (σ : V → Bool) (φ : CNF V) : Bool := φ.all (Clause.evalOne σ) + +/-- Some assignment gives exactly one true literal occurrence in every clause. -/ +def CNF.OneSatisfiable (φ : CNF V) : Prop := ∃ σ : V → Bool, φ.evalOne σ = true + +/-- The empty clause cannot have exactly one true literal. -/ +@[simp] theorem Clause.evalOne_nil (σ : V → Bool) : Clause.evalOne σ [] = false := rfl + +/-- A singleton exact-one clause has the same semantics as its literal. -/ +@[simp] theorem Clause.evalOne_singleton (σ : V → Bool) (l : Literal V) : + Clause.evalOne σ [l] = Literal.eval σ l := by + cases h : Literal.eval σ l <;> simp [Clause.evalOne, h] + +/-- Exact-one satisfaction is the occurrence-count equation. -/ +theorem Clause.evalOne_eq_true_iff (σ : V → Bool) (c : Clause V) : + c.evalOne σ = true ↔ c.countP (Literal.eval σ) = 1 := by + simp [Clause.evalOne] + +/-- An assignment satisfies the exact-one formula when it satisfies every exact-one clause. -/ +theorem CNF.evalOne_eq_true_iff (σ : V → Bool) (φ : CNF V) : + φ.evalOne σ = true ↔ ∀ c ∈ φ, c.evalOne σ = true := by + simp [CNF.evalOne] + +/-- The empty exact-one formula is true. -/ +@[simp] theorem CNF.evalOne_nil (σ : V → Bool) : CNF.evalOne σ [] = true := rfl + +/-- A cons of exact-one clauses evaluates conjunctively. -/ +@[simp] theorem CNF.evalOne_cons (σ : V → Bool) (c : Clause V) (φ : CNF V) : + CNF.evalOne σ (c :: φ) = (c.evalOne σ && φ.evalOne σ) := rfl + +/-- Appending exact-one formulas conjoins their values. -/ +@[simp] theorem CNF.evalOne_append (σ : V → Bool) (φ ψ : CNF V) : + CNF.evalOne σ (φ ++ ψ) = (φ.evalOne σ && ψ.evalOne σ) := by + simp [CNF.evalOne] + +/-- A blockwise exact-one formula holds when every block does. -/ +theorem CNF.evalOne_flatMap_eq_true_iff {ι : Type*} (σ : V → Bool) (l : List ι) + (f : ι → CNF V) : + CNF.evalOne σ (l.flatMap f) = true ↔ ∀ i ∈ l, (f i).evalOne σ = true := by + simp only [CNF.evalOne_eq_true_iff, List.mem_flatMap, forall_exists_index, and_imp] + exact ⟨fun h i hi c hc => h c i hi hc, fun h c i hi hc => h i hi c hc⟩ + +/-- Clause exact-one evaluation depends only on the variables occurring in it. -/ +theorem Clause.evalOne_congr {σ τ : V → Bool} {c : Clause V} + (h : ∀ l ∈ c, σ l.1 = τ l.1) : c.evalOne σ = c.evalOne τ := by + unfold Clause.evalOne + congr 1 + apply List.countP_congr + intro l hl + simp only [Literal.eval, h l hl] + +/-- Formula exact-one evaluation depends only on its variables. -/ +theorem CNF.evalOne_congr {σ τ : V → Bool} {φ : CNF V} + (h : ∀ v ∈ φ.vars, σ v = τ v) : φ.evalOne σ = φ.evalOne τ := by + rw [Bool.eq_iff_iff, CNF.evalOne_eq_true_iff, CNF.evalOne_eq_true_iff] + refine forall₂_congr fun c hc => ?_ + rw [Clause.evalOne_congr (fun l hl => h l.1 ?_)] + exact CNF.mem_vars.mpr ⟨c, hc, l.2, hl⟩ + +/-- Exact-one clause evaluation commutes with relabelling. -/ +@[simp] theorem Clause.evalOne_relabel (f : V → W) (σ : W → Bool) (c : Clause V) : + (c.relabel f).evalOne σ = c.evalOne (σ ∘ f) := by + unfold Clause.evalOne Clause.relabel + rw [List.countP_map] + rfl + +/-- Exact-one formula evaluation commutes with relabelling. -/ +@[simp] theorem CNF.evalOne_relabel (f : V → W) (σ : W → Bool) (φ : CNF V) : + (φ.relabel f).evalOne σ = φ.evalOne (σ ∘ f) := by + simp [CNF.evalOne, CNF.relabel, List.all_map, Function.comp_def] + +/-- Clauses forbidding simultaneous truth of each pair of literal positions. -/ +def Clause.atMostOneOccurrences : Clause V → CNF V + | [] => [] + | l :: c => c.map (fun k => [Std.Sat.Literal.negate l, Std.Sat.Literal.negate k]) ++ + atMostOneOccurrences c + +/-- Express exact-one clause semantics with ordinary clauses and no new variables. -/ +def Clause.oneEncoding (c : Clause V) : CNF V := [c] ++ c.atMostOneOccurrences + +/-- Express exact-one formula semantics with ordinary clauses and no new variables. -/ +def CNF.oneEncoding (φ : CNF V) : CNF V := φ.flatMap Clause.oneEncoding + +/-- The pair clauses constrain the true occurrence count to be at most one. -/ +theorem Clause.eval_atMostOneOccurrences (σ : V → Bool) (c : Clause V) : + c.atMostOneOccurrences.eval σ = true ↔ c.countP (Literal.eval σ) ≤ 1 := by + induction c with + | nil => simp [Clause.atMostOneOccurrences] + | cons l c ih => + have head (l : Literal V) : + CNF.eval σ (c.map fun k => [Std.Sat.Literal.negate l, Std.Sat.Literal.negate k]) = + true ↔ (Literal.eval σ l = true → c.countP (Literal.eval σ) = 0) := by + cases h : Literal.eval σ l <;> + simp [CNF.eval, Clause.eval, List.countP_eq_zero, h] + rw [Clause.atMostOneOccurrences, CNF.eval_append, Bool.and_eq_true, head, ih] + cases h : Literal.eval σ l <;> + simp only [List.countP_cons, h, Bool.false_eq_true, ↓reduceIte, + false_implies, true_implies, true_and] <;> omega + +/-- Exact-one clause encoding preserves evaluation under the same assignment. -/ +@[simp] theorem Clause.eval_oneEncoding (σ : V → Bool) (c : Clause V) : + c.oneEncoding.eval σ = c.evalOne σ := by + rw [Bool.eq_iff_iff, Clause.oneEncoding, CNF.eval_append, Bool.and_eq_true] + rw [CNF.eval_cons, CNF.eval_nil, Bool.and_true, Clause.eval_atMostOneOccurrences, + Clause.evalOne_eq_true_iff] + have hpos : c.eval σ = true ↔ 0 < c.countP (Literal.eval σ) := by + simp [Clause.eval, List.countP_pos_iff] + rw [hpos] + omega + +/-- Exact-one formula encoding preserves evaluation under the same assignment. -/ +@[simp] theorem CNF.eval_oneEncoding (σ : V → Bool) (φ : CNF V) : + φ.oneEncoding.eval σ = φ.evalOne σ := by + rw [Bool.eq_iff_iff, CNF.oneEncoding, CNF.eval_flatMap_eq_true_iff, + CNF.evalOne_eq_true_iff] + simp + +/-- The ordinary encoding is satisfiable exactly when the exact-one source is. -/ +theorem CNF.satisfiable_oneEncoding (φ : CNF V) : + φ.oneEncoding.Satisfiable ↔ φ.OneSatisfiable := by + simp [CNF.Satisfiable, CNF.OneSatisfiable] + +/-- A variable in a pair clause already occurs in the source clause. -/ +theorem Clause.fst_mem_of_mem_atMostOneOccurrences {c : Clause V} {d : Clause V} + (hd : d ∈ c.atMostOneOccurrences) {l : Literal V} (hl : l ∈ d) : + ∃ k ∈ c, l.1 = k.1 := by + induction c with + | nil => simp [Clause.atMostOneOccurrences] at hd + | cons k c ih => + simp only [Clause.atMostOneOccurrences, List.mem_append, List.mem_map] at hd + rcases hd with ⟨k', hk', rfl⟩ | hd + · simp only [List.mem_cons, List.not_mem_nil, or_false] at hl + rcases hl with rfl | rfl + · exact ⟨k, List.mem_cons_self, rfl⟩ + · exact ⟨k', List.mem_cons_of_mem _ hk', rfl⟩ + · obtain ⟨k', hk', he⟩ := ih hd + exact ⟨k', List.mem_cons_of_mem _ hk', he⟩ + +/-- The encoding has exactly the variables of the source, irrespective of repetitions. -/ +theorem CNF.mem_vars_oneEncoding {φ : CNF V} {v : V} : + v ∈ φ.oneEncoding.vars ↔ v ∈ φ.vars := by + constructor + · intro hv + obtain ⟨d, hd, b, hl⟩ := CNF.mem_vars.mp hv + obtain ⟨c, hc, hd⟩ := List.mem_flatMap.mp hd + rcases List.mem_append.mp hd with hd | hd + · have hd' : d = c := List.mem_singleton.mp hd + subst d + exact CNF.mem_vars.mpr ⟨c, hc, b, hl⟩ + · obtain ⟨k, hk, he⟩ := Clause.fst_mem_of_mem_atMostOneOccurrences hd hl + have hk' : (v, k.2) = k := Prod.ext he rfl + exact CNF.mem_vars.mpr ⟨c, hc, k.2, hk'.symm ▸ hk⟩ + · intro hv + obtain ⟨c, hc, b, hl⟩ := CNF.mem_vars.mp hv + exact CNF.mem_vars.mpr ⟨c, List.mem_flatMap.mpr + ⟨c, hc, List.mem_append.mpr (Or.inl (List.mem_singleton_self _))⟩, b, hl⟩ + +/-- The pair encoding commutes with renaming variables. -/ +theorem Clause.atMostOneOccurrences_relabel (f : V → W) (c : Clause V) : + (c.relabel f).atMostOneOccurrences = c.atMostOneOccurrences.relabel f := by + induction c with + | nil => rfl + | cons l c ih => + simp only [Clause.relabel, CNF.relabel] at ih + simp [Clause.relabel, Clause.atMostOneOccurrences, CNF.relabel, ih, + List.map_map, Function.comp_def, Std.Sat.Literal.negate] + +/-- The clause encoding commutes with renaming variables. -/ +theorem Clause.oneEncoding_relabel (f : V → W) (c : Clause V) : + (c.relabel f).oneEncoding = c.oneEncoding.relabel f := by + simp [Clause.oneEncoding, Clause.atMostOneOccurrences_relabel, CNF.relabel] + +/-- The formula encoding commutes with renaming variables. -/ +theorem CNF.oneEncoding_relabel (f : V → W) (φ : CNF V) : + (φ.relabel f).oneEncoding = φ.oneEncoding.relabel f := by + induction φ with + | nil => rfl + | cons c φ ih => + simp only [CNF.relabel, CNF.oneEncoding] at ih + simp [CNF.relabel, CNF.oneEncoding, Clause.oneEncoding_relabel, ih] + +end Cslib.Logic.PL diff --git a/Complexity/Logics/Propositional/CNF/ExactOne/BlockBounds.lean b/Complexity/Logics/Propositional/CNF/ExactOne/BlockBounds.lean new file mode 100644 index 000000000..badaf7b0c --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/ExactOne/BlockBounds.lean @@ -0,0 +1,87 @@ +/- +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.ExactOne.Blocks + +/-! +# Disjoint index intervals of compiled clause blocks + +Local positive auxiliary names carry an index in their original signed clause block. Adding +the preceding signed-block lengths gives disjoint intervals for distinct original clauses. + +## Main results + +* `PositiveExactOne.blockOffset_mono`, `PositiveExactOne.blockOffset_succ`. +* `PositiveExactOne.blockOffset_range_injective`: a global index identifies its block and slot. +* `PositiveExactOneCompile.signedOffset_range_injective`: the actual compiler specialization. + +## References + +These are original list-index identities for the explicit compiler. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.PositiveExactOne + +variable {V : Type*} + +/-- Prefix lengths are monotone, without requiring the blocks to be nonempty. -/ +theorem blockOffset_mono (bs : List (CNF V)) {i j : ℕ} (hij : i ≤ j) : + blockOffset bs i ≤ blockOffset bs j := by + unfold blockOffset + conv_rhs => rw [show j = i + (j - i) by omega] + rw [List.take_add, List.map_append, List.sum_append] + exact Nat.le_add_right _ _ + +/-- Passing an existing block adds exactly its number of signed clauses. -/ +theorem blockOffset_succ (bs : List (CNF V)) {i : ℕ} (hi : i < bs.length) : + blockOffset bs (i + 1) = blockOffset bs i + bs[i].length := by + simp only [blockOffset, List.take_succ_eq_append_getElem hi, List.map_append, + List.map_cons, List.map_nil, List.sum_append, List.sum_cons, List.sum_nil, Nat.add_zero] + +/-- Local index ranges of different blocks are disjoint. No positive-length assumption on +unrelated blocks is needed. -/ +theorem blockOffset_range_injective (bs : List (CNF V)) {i j a b : ℕ} + (hi : i < bs.length) (hj : j < bs.length) (ha : a < bs[i].length) + (hb : b < bs[j].length) (he : blockOffset bs i + a = blockOffset bs j + b) : + i = j ∧ a = b := by + have hij : i = j := by + rcases lt_trichotomy i j with hij | hij | hij + · have hm := blockOffset_mono bs (show i + 1 ≤ j by omega) + rw [blockOffset_succ bs hi] at hm + omega + · exact hij + · have hm := blockOffset_mono bs (show j + 1 ≤ i by omega) + rw [blockOffset_succ bs hj] at hm + omega + subst j + exact ⟨rfl, by omega⟩ + +end Cslib.Logic.PL.CNF.PositiveExactOne + +namespace Cslib.Logic.PL.CNF.PositiveExactOneCompile + +variable {V : Type*} + +/-- In the actual compiler, a positive auxiliary's global signed-clause position uniquely +identifies its original clause block and local signed-clause position. -/ +theorem signedOffset_range_injective (φ : CNF V) {i j a b : ℕ} + (hi : i < φ.length) (hj : j < φ.length) + (ha : a < (FromSAT.clause i φ[i]).length) + (hb : b < (FromSAT.clause j φ[j]).length) + (he : signedOffset φ i + a = signedOffset φ j + b) : i = j ∧ a = b := by + let bs := φ.zipIdx.map fun c => FromSAT.clause c.2 c.1 + have hi' : i < bs.length := by simpa [bs] using hi + have hj' : j < bs.length := by simpa [bs] using hj + have ha' : a < bs[i].length := by simpa [bs] using ha + have hb' : b < bs[j].length := by simpa [bs] using hb + exact PositiveExactOne.blockOffset_range_injective bs hi' hj' ha' hb' he + +end Cslib.Logic.PL.CNF.PositiveExactOneCompile diff --git a/Complexity/Logics/Propositional/CNF/ExactOne/Blocks.lean b/Complexity/Logics/Propositional/CNF/ExactOne/Blocks.lean new file mode 100644 index 000000000..1783ba17b --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/ExactOne/Blocks.lean @@ -0,0 +1,185 @@ +/- +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.ExactOne.Compile + +/-! +# Exact block indexing for positive clause replacements + +The positive compiler numbers auxiliaries by the position of their signed source clause. +When signed clauses are themselves emitted in blocks, local drawings use local clause indices. +Shifting those indices by the preceding signed block lengths gives exactly the original +compiler's formula, including its variable names and clause order. + +## Main definitions + +* `PositiveExactOne.shift`: translate the signed-clause index of positive auxiliaries. +* `PositiveExactOne.formulaFrom`: the positive compiler starting at a supplied clause index. +* `PositiveExactOne.compileBlocks`: compile signed blocks with their actual global offsets. + +## Main results + +* `PositiveExactOne.formula_flatten`: exact global formula as separately compiled blocks. +* `PositiveExactOneCompile.rawFormula_eq_blocks`: partition by original source clauses. + +## References + +These are original indexing identities for the two local compilers. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.PositiveExactOne + +variable {V : Type*} + +/-- Add the signed-clause offset to new positive auxiliaries, fixing source variables. -/ +def shift (offset : ℕ) : Variable V → Variable V + | .inl v => .inl v + | .inr ((i, j), k) => .inr ((offset + i, j), k) + +/-- Compile a signed formula whose first clause has the given global index. -/ +def formulaFrom (start : ℕ) (φ : CNF V) : CNF (Variable V) := + φ.zipIdx start |>.flatMap fun c => clauseClauses c.2 c.1 + +/-- Shifting preserves the source role of every terminal and shifts each private slot. -/ +theorem positiveLiteral_shift (offset i j : ℕ) (l : Literal V) : + ((shift offset) (positiveLiteral i j l).1, (positiveLiteral i j l).2) = + positiveLiteral (offset + i) j l := by + cases l with + | mk v b => cases b <;> rfl + +/-- A complement block is equivariant under shifting its signed-clause index. -/ +theorem complementBlock_shift (offset i j : ℕ) (l : Literal V) : + (complementBlock i j l).relabel (shift offset) = complementBlock (offset + i) j l := by + cases l with + | mk v b => cases b <;> rfl + +/-- The entire local clause replacement shifts to its actual global clause index. -/ +theorem clauseClauses_shift (offset i : ℕ) (c : Clause V) : + (clauseClauses i c).relabel (shift offset) = clauseClauses (offset + i) c := by + simp only [clauseClauses, CNF.relabel, List.map_append, List.map_cons, List.map_nil, + Clause.relabel, List.map_map, List.map_flatMap] + apply congrArg₂ (· ++ ·) + · apply congrArg (fun d => [d]) + apply List.map_congr_left + intro l _ + exact positiveLiteral_shift _ _ _ _ + · simp only [List.flatMap_def] + apply congrArg List.flatten + apply List.map_congr_left + intro l _ + exact complementBlock_shift _ _ _ _ + +/-- The indexed list at a shifted starting position is the original list with shifted indices. -/ +private theorem zipIdx_start {A : Type*} (xs : List A) (start offset : ℕ) : + xs.zipIdx (offset + start) = (xs.zipIdx start).map fun c => (c.1, offset + c.2) := by + induction xs generalizing start with + | nil => rfl + | cons a xs ih => simp [List.zipIdx_cons, ih, Nat.add_assoc] + +/-- Starting a compiler at a nonzero index renames just its fresh auxiliaries. -/ +theorem formulaFrom_eq_relabel (start : ℕ) (φ : CNF V) : + formulaFrom start φ = (formula φ).relabel (shift start) := by + unfold formulaFrom formula + rw [show φ.zipIdx start = (φ.zipIdx 0).map (fun c => (c.1, start + c.2)) from + by simpa only [Nat.add_zero] using zipIdx_start φ 0 start] + simp only [CNF.relabel, List.flatMap_def, List.map_flatten, List.map_map, + Function.comp_def] + apply congrArg List.flatten + apply List.map_congr_left + intro c _ + exact (clauseClauses_shift _ _ _).symm + +/-- Compiling a concatenation gives the second part its exact signed-clause offset. -/ +theorem formulaFrom_append (start : ℕ) (φ ψ : CNF V) : + formulaFrom start (φ ++ ψ) = formulaFrom start φ ++ formulaFrom (start + φ.length) ψ := by + simp only [formulaFrom, List.zipIdx_append, List.flatMap_append] + +/-- Compile consecutive signed blocks with their true cumulative signed-clause offsets. -/ +def compileBlocks (start : ℕ) : List (CNF V) → CNF (Variable V) + | [] => [] + | b :: bs => (formula b).relabel (shift start) ++ compileBlocks (start + b.length) bs + +/-- Number of signed clauses before one block. -/ +def blockOffset (bs : List (CNF V)) (i : ℕ) : ℕ := + ((bs.take i).map List.length).sum + +/-- The recursive partition is also an indexed concatenation with explicit prefix offsets. -/ +theorem compileBlocks_eq_flatMap (start : ℕ) (bs : List (CNF V)) : + compileBlocks start bs = bs.zipIdx.flatMap fun b => + (formula b.1).relabel (shift (start + blockOffset bs b.2)) := by + induction bs generalizing start with + | nil => rfl + | cons b bs ih => + simp only [compileBlocks, List.zipIdx_cons, List.flatMap_cons] + rw [ih] + have he : bs.zipIdx 1 = (bs.zipIdx 0).map (fun c => (c.1, 1 + c.2)) := + zipIdx_start bs 0 1 + rw [he, List.flatMap_map] + congr 1 + simp only [List.flatMap_def] + apply congrArg List.flatten + apply List.map_congr_left + intro c _ + simp [blockOffset, Nat.add_assoc, Nat.one_add] + +/-- The blockwise compiler is exactly the indexed compiler on the flattened signed input. -/ +theorem formulaFrom_flatten (start : ℕ) (bs : List (CNF V)) : + formulaFrom start bs.flatten = compileBlocks start bs := by + induction bs generalizing start with + | nil => rfl + | cons b bs ih => + rw [List.flatten_cons, formulaFrom_append, formulaFrom_eq_relabel, ih] + rfl + +/-- Exact formula and fresh-variable names for the block partition at index zero. -/ +theorem formula_flatten (bs : List (CNF V)) : + formula bs.flatten = compileBlocks 0 bs := + formulaFrom_flatten 0 bs + +end Cslib.Logic.PL.CNF.PositiveExactOne + +namespace Cslib.Logic.PL.CNF.PositiveExactOneCompile + +variable {V : Type*} + +/-- The actual positive formula is the concatenation of locally compiled source-clause +profiles, with outer auxiliaries shifted by signed-block lengths. -/ +theorem rawFormula_eq_blocks (φ : CNF V) : + rawFormula φ = PositiveExactOne.compileBlocks 0 + (φ.zipIdx.map fun c => FromSAT.clause c.2 c.1) := by + exact PositiveExactOne.formula_flatten _ + +/-- The actual signed-clause offset of the replacement of original clause `i`. -/ +def signedOffset (φ : CNF V) (i : ℕ) : ℕ := + PositiveExactOne.blockOffset (φ.zipIdx.map fun c => FromSAT.clause c.2 c.1) i + +/-- Exact original-clause partition, in source order, with actual global auxiliary names. -/ +theorem rawFormula_eq_flatMap (φ : CNF V) : + rawFormula φ = φ.zipIdx.flatMap fun c => + (PositiveExactOne.formula (FromSAT.clause c.2 c.1)).relabel + (PositiveExactOne.shift (signedOffset φ c.2)) := by + rw [rawFormula_eq_blocks, PositiveExactOne.compileBlocks_eq_flatMap] + simp only [List.zipIdx_map, List.flatMap_map, Nat.zero_add, signedOffset] + simp only [List.flatMap_def] + apply congrArg List.flatten + have map_index (f : Clause V × ℕ → CNF (Variable V)) : + (φ.zipIdx.map f) = (φ.zipIdx.zipIdx.map fun c => f c.1) := by + simpa only [List.map_map, Function.comp_def] using + congrArg (List.map f) (List.zipIdx_map_fst 0 φ.zipIdx).symm + rw [map_index] + apply List.map_congr_left + intro ⟨⟨c, i⟩, j⟩ hc + have hi := List.mem_zipIdx_iff_getElem?.mp hc + obtain ⟨h, he⟩ := List.getElem?_eq_some_iff.mp hi + have hij : i = j := by simpa using (congrArg Prod.snd he).symm + simp only [Prod.map_fst, Prod.map_snd, id_eq, hij] + +end Cslib.Logic.PL.CNF.PositiveExactOneCompile diff --git a/Complexity/Logics/Propositional/CNF/ExactOne/Compile.lean b/Complexity/Logics/Propositional/CNF/ExactOne/Compile.lean new file mode 100644 index 000000000..3606b2664 --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/ExactOne/Compile.lean @@ -0,0 +1,172 @@ +/- +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.ExactOne.FromSAT +public import Complexity.Logics.Propositional.CNF.ExactOne.Positive +public import Complexity.Logics.Propositional.CNF.Compact.Relabel +import Mathlib.Tactic.Linarith + +/-! +# Constant-factor parsimonious compilation to positive exact-one clauses + +The two local constructions compose without another occurrence splitter or planarization. +Every source clause contributes at most thirteen signed exact-one clauses; replacing their +negative occurrences contributes at most thirteen times as many positive clauses. These are +bounds on actual emitted lists. Every source variable keeps its occurrence count and no new +variable occurs more than three times. + +## Main definitions + +* `PositiveExactOneCompile.rawFormula`: the structured-variable positive compiler. +* `PositiveExactOneCompile.formula`: its compact natural-variable output. + +## Main results + +* `oneModelCount_formula`, `oneSatisfiable_formula`: parsimony and correctness. +* `length_formula_le`, `length_vars_formula_le`: explicit constant-factor bounds. + +## References + +The local gadgets are from [HuntMaratheRadhakrishnanStearns1998, Theorem 3.8]. +The composition and quantitative bounds are original. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.PositiveExactOneCompile + +variable {V : Type*} + +/-- The two disjoint families of local auxiliary variables. -/ +abbrev Variable (V : Type*) := PositiveExactOne.Variable (FromSAT.Variable V) + +/-- Replace disjunctions by uniquely extensible exact-one clauses and remove all negations. -/ +def rawFormula (φ : CNF V) : CNF (Variable V) := + PositiveExactOne.formula (FromSAT.formula φ) + +/-- Compact natural names for all output variables. -/ +def formula [DecidableEq V] (φ : CNF V) : CNF ℕ := (rawFormula φ).compact + +/-- Both local stages have constant size: at most 169 clauses per source clause. -/ +theorem length_rawFormula_le (φ : CNF V) : (rawFormula φ).length ≤ 169 * φ.length := by + have h := PositiveExactOne.length_formula_le + (fun c hc => (FromSAT.width_formula φ c hc).le) + have hs := FromSAT.length_formula_le φ + change (rawFormula φ).length ≤ _ at h + nlinarith + +/-- Every emitted clause has exactly three literal occurrences. -/ +theorem width_rawFormula (φ : CNF V) : ∀ c ∈ rawFormula φ, c.length = 3 := + PositiveExactOne.exactWidth_formula (FromSAT.width_formula φ) + +/-- All output literal polarities are positive. -/ +theorem positive_rawFormula (φ : CNF V) : + ∀ c ∈ rawFormula φ, ∀ l ∈ c, l.2 = true := + PositiveExactOne.positive_formula _ + +/-- Every emitted clause has three distinct variables, including when source literals repeat. -/ +theorem nodup_rawFormula (φ : CNF V) : + ∀ c ∈ rawFormula φ, (c.map Prod.fst).Nodup := by + apply PositiveExactOne.nodup_formula + intro c hc + obtain ⟨⟨a, i⟩, _, hc⟩ := List.mem_flatMap.mp hc + exact FromSAT.nodup_clause i a c hc + +/-- Occurrence-three inputs retain that bound through both local replacements. -/ +theorem count_vars_rawFormula_le [DecidableEq V] (φ : CNF V) + (hw : ∀ c ∈ φ, c.length ≤ 3) (ho : ∀ v, φ.vars.count v ≤ 3) : + ∀ v, (rawFormula φ).vars.count v ≤ 3 := by + have eqCount {A : Type _} (b₁ b₂ : BEq A) (h₁ : @LawfulBEq A b₁) + (h₂ : @LawfulBEq A b₂) (as : List A) (a : A) : + @List.count A b₁ a as = @List.count A b₂ a as := by + apply List.countP_congr + intro x _ + exact (@beq_iff_eq A b₁ h₁ x a).trans (@beq_iff_eq A b₂ h₂ x a).symm + have h := PositiveExactOne.count_vars_formula_le (FromSAT.formula φ) (fun v => by + have hv := FromSAT.count_vars_formula_le φ hw ho v + have he := eqCount (instBEqOfDecidableEq : BEq (FromSAT.Variable V)) + (inferInstance : BEq (FromSAT.Variable V)) inferInstance inferInstance + (FromSAT.formula φ).vars v + exact he.le.trans hv) + intro v + have hv := h v + have he := eqCount (inferInstance : BEq (Variable V)) + (@Sum.instBEq (FromSAT.Variable V) ((ℕ × ℕ) × ℕ) instBEqOfDecidableEq inferInstance) + inferInstance inferInstance (rawFormula φ).vars v + exact he.le.trans hv + +/-- Model counts are preserved through both uniquely extensible stages. -/ +theorem oneModelCount_rawFormula [DecidableEq V] (φ : CNF V) + (hw : ∀ c ∈ φ, c.length ≤ 3) : (rawFormula φ).oneModelCount = φ.modelCount := + (PositiveExactOne.oneModelCount_formula _).trans (FromSAT.oneModelCount_formula φ hw) + +/-- Compact numbering preserves the constant-factor clause bound. -/ +theorem length_formula_le [DecidableEq V] (φ : CNF V) : + (formula φ).length ≤ 169 * φ.length := by + simpa only [formula, length_compact] using length_rawFormula_le φ + +/-- Every compact output clause has exactly three literal occurrences. -/ +theorem width_formula [DecidableEq V] (φ : CNF V) : ∀ c ∈ formula φ, c.length = 3 := by + intro c hc + obtain ⟨d, hd, rfl⟩ := List.mem_map.mp hc + simpa only [Clause.relabel, List.length_map] using width_rawFormula φ d hd + +/-- Compact numbering does not change the positive polarity of any literal. -/ +theorem positive_formula [DecidableEq V] (φ : CNF V) : + ∀ c ∈ formula φ, ∀ l ∈ c, l.2 = true := by + intro c hc l hl + obtain ⟨d, hd, rfl⟩ := List.mem_map.mp hc + obtain ⟨a, ha, rfl⟩ := List.mem_map.mp hl + exact positive_rawFormula φ d hd a ha + +/-- Compaction preserves distinct variables within every output clause. -/ +theorem nodup_formula [DecidableEq V] (φ : CNF V) : + ∀ c ∈ formula φ, (c.map Prod.fst).Nodup := by + intro c hc + let : BEq (Variable V) := instBEqOfDecidableEq + obtain ⟨d, hd, rfl⟩ := List.mem_map.mp hc + have hn := nodup_rawFormula φ d hd + have he : ((d.relabel (rawFormula φ).vars.idxOf).map Prod.fst) = + (d.map Prod.fst).map (rawFormula φ).vars.idxOf := by + simp [Clause.relabel, List.map_map, Function.comp_def] + change ((d.relabel (rawFormula φ).vars.idxOf).map Prod.fst).Nodup + rw [he] + change List.Pairwise _ _ at hn ⊢ + rw [List.pairwise_map] + apply hn.imp_of_mem + intro a b ha _ hab heq + have hsource : a ∈ (rawFormula φ).vars := by + obtain ⟨l, hl, rfl⟩ := List.mem_map.mp ha + exact CNF.mem_vars.mpr ⟨d, hd, l.2, hl⟩ + exact hab ((List.idxOf_inj hsource).mp heq) + +/-- The actual output has at most 507 literal occurrences per source clause. -/ +theorem length_vars_formula_le [DecidableEq V] (φ : CNF V) : + (formula φ).vars.length ≤ 507 * φ.length := by + have h := CNF.length_vars_le_mul_length (fun c hc => (width_formula φ c hc).le) + have hs := length_formula_le φ + nlinarith + +/-- Compact numbering preserves the degree-three restriction. -/ +theorem count_vars_formula_le [DecidableEq V] (φ : CNF V) + (hw : ∀ c ∈ φ, c.length ≤ 3) (ho : ∀ v, φ.vars.count v ≤ 3) : + ∀ v, (formula φ).vars.count v ≤ 3 := + CNF.count_vars_compact_le _ (count_vars_rawFormula_le φ hw ho) + +/-- The final natural-variable output has exactly the source model count. -/ +theorem oneModelCount_formula [DecidableEq V] (φ : CNF V) + (hw : ∀ c ∈ φ, c.length ≤ 3) : (formula φ).oneModelCount = φ.modelCount := + (CNF.oneModelCount_compact _).trans (oneModelCount_rawFormula φ hw) + +/-- The compiler preserves satisfiability between ordinary and exact-one semantics. -/ +theorem oneSatisfiable_formula [DecidableEq V] (φ : CNF V) + (hw : ∀ c ∈ φ, c.length ≤ 3) : (formula φ).OneSatisfiable ↔ φ.Satisfiable := by + rw [← CNF.oneModelCount_pos_iff, oneModelCount_formula φ hw, CNF.modelCount_pos_iff] + +end Cslib.Logic.PL.CNF.PositiveExactOneCompile diff --git a/Complexity/Logics/Propositional/CNF/ExactOne/FromSAT.lean b/Complexity/Logics/Propositional/CNF/ExactOne/FromSAT.lean new file mode 100644 index 000000000..81838e87f --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/ExactOne/FromSAT.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.Logics.Propositional.CNF.ExactOne.Gadgets +public import Complexity.Logics.Propositional.CNF.ExactOne.ModelCount +import Mathlib.Algebra.BigOperators.Group.List.Lemmas +import Mathlib.Data.List.Sigma +import Mathlib.Algebra.Order.BigOperators.Group.List +import Mathlib.Algebra.BigOperators.Ring.List +import Mathlib.Tactic.IntervalCases + +/-! +# Parsimonious conversion of ordinary clauses to exact-one clauses + +Each clause of width at most three is replaced by the four-clause disjunction gadget of +[Hunt, Marathe, Radhakrishnan and Stearns, Theorem 3.8, Figure 3.4] +[HuntMaratheRadhakrishnanStearns1998]. Missing literals are replaced by distinct, uniquely +forced false variables. Unlike repeating a source literal, this preserves the number of +occurrences of every source variable. Even an empty clause has an exact-three, unsatisfiable +output. Clauses longer than three are truncated; correctness therefore requires the width bound. + +## Main definitions + +* `FromSAT.clause`: the constant-size replacement of one clause. +* `FromSAT.formula`: replace every clause with separately indexed auxiliaries. +* `FromSAT.assignment`: the canonical extension of a source assignment. + +## Main results + +* `FromSAT.length_clause_le`, `FromSAT.length_formula_le`: constant-factor clause bounds. + +## References + +* [Hunt, Marathe, Radhakrishnan and Stearns][HuntMaratheRadhakrishnanStearns1998], + Theorem 3.8 and Lemma 3.6. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.FromSAT + +open ExactOneGadget + +variable {V : Type*} + +/-- Source variables and clause-indexed auxiliary slots. -/ +abbrev Variable (V : Type*) := Sum V (ℕ × ℕ) + +/-- An auxiliary slot local to one source clause. -/ +def aux (i k : ℕ) : Variable V := Sum.inr (i, k) + +/-- Embed a signed source literal. -/ +def lift (l : Literal V) : Literal (Variable V) := (Sum.inl l.1, l.2) + +/-- A positive padding literal, used only when the corresponding source position is absent. -/ +def padding (i k : ℕ) : Literal (Variable V) := (aux i k, true) + +/-- The uniquely extensible disjunction gadget in local slots three through seven. -/ +def core (i : ℕ) (p q r : Literal (Variable V)) : CNF (Variable V) := + disjunctionClauses p q r (aux i 3) (aux i 4) (aux i 5) (aux i 6) (aux i 7) + +/-- Force padding slot `k` false, using its own three auxiliary slots. -/ +def forcePadding (i k : ℕ) : CNF (Variable V) := + forceFalseClauses (aux i k) (aux i (8 + 3 * k)) + (aux i (9 + 3 * k)) (aux i (10 + 3 * k)) + +/-- Replace one clause by exact-three exact-one clauses, retaining every source occurrence once. -/ +def clause (i : ℕ) : Clause V → CNF (Variable V) + | [] => core i (padding i 0) (padding i 1) (padding i 2) ++ + forcePadding i 0 ++ forcePadding i 1 ++ forcePadding i 2 + | [p] => core i (lift p) (padding i 1) (padding i 2) ++ + forcePadding i 1 ++ forcePadding i 2 + | [p, q] => core i (lift p) (lift q) (padding i 2) ++ forcePadding i 2 + | p :: q :: r :: _ => core i (lift p) (lift q) (lift r) + +/-- Replace the source clauses, distinguishing auxiliaries by clause position. -/ +def formula (φ : CNF V) : CNF (Variable V) := + φ.zipIdx.flatMap fun c => clause c.2 c.1 + +/-- The source truth value at a clause position; absent positions are false. -/ +def sourceValue (σ : V → Bool) (c : Clause V) (k : ℕ) : Bool := + (c[k]?).elim false (Literal.eval σ) + +/-- Canonical auxiliary values, including all three possible false-padding blocks. -/ +def auxiliaryValue (σ : V → Bool) (c : Clause V) (k : ℕ) : Bool := + let p := sourceValue σ c 0 + let q := sourceValue σ c 1 + let r := sourceValue σ c 2 + if k = 3 then firstAux p q + else if k = 4 then secondAux p q + else if k = 5 then thirdAux p q + else if k = 6 then fourthAux p q + else if k = 7 then fifthAux p q r + else decide (k = 9 ∨ k = 12 ∨ k = 15) + +/-- Extend an assignment by the uniquely determined values of all occurring auxiliaries. -/ +def assignment (φ : CNF V) (σ : V → Bool) : Variable V → Bool + | Sum.inl v => σ v + | Sum.inr (i, k) => auxiliaryValue σ (φ[i]?.getD []) k + +/-- A clause contributes between four and thirteen clauses. -/ +theorem length_clause_le (i : ℕ) (c : Clause V) : (clause i c).length ≤ 13 := by + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ <;> + simp [clause, core, disjunctionClauses, forcePadding, forceFalseClauses] + +/-- Every output clause has exactly three literal occurrences. -/ +theorem width_clause (i : ℕ) (c : Clause V) : + ∀ d ∈ clause i c, d.length = 3 := by + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ <;> + simp [clause, core, disjunctionClauses, forcePadding, forceFalseClauses] + +/-- Clause replacement increases the number of clauses by at most a factor of thirteen. -/ +theorem length_formula_le (φ : CNF V) : (formula φ).length ≤ 13 * φ.length := by + calc + _ = (φ.zipIdx.map fun c => (clause c.2 c.1).length).sum := List.length_flatMap + _ ≤ (φ.zipIdx.map fun _ => 13).sum := List.sum_le_sum fun _ _ => length_clause_le _ _ + _ = _ := by simp [Nat.mul_comm] + +/-- Every output clause is exact-three, including on malformed source inputs. -/ +theorem width_formula (φ : CNF V) : ∀ c ∈ formula φ, c.length = 3 := by + intro c hc + obtain ⟨d, _, hc⟩ := List.mem_flatMap.mp hc + exact width_clause d.2 d.1 c hc + +/-- Every satisfying replacement restricts to a satisfying source clause. -/ +theorem clause_sound (i : ℕ) (c : Clause V) (hc : c.length ≤ 3) + (τ : Variable V → Bool) (hτ : (clause i c).evalOne τ = true) : + c.eval (fun v => τ (Sum.inl v)) = true := by + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ + · simp only [clause, CNF.evalOne_append, Bool.and_eq_true, core, + evalOne_disjunctionClauses_iff_canonical, forcePadding, + evalOne_forceFalseClauses_iff] at hτ + obtain ⟨⟨⟨hcore, h₀⟩, h₁⟩, h₂⟩ := hτ + simpa [padding, Literal.eval, h₀.1, h₁.1, h₂.1] using hcore.1 + · simp only [clause, CNF.evalOne_append, Bool.and_eq_true, core, + evalOne_disjunctionClauses_iff_canonical, forcePadding, + evalOne_forceFalseClauses_iff] at hτ + obtain ⟨⟨hcore, h₁⟩, h₂⟩ := hτ + simpa [padding, lift, Literal.eval, h₁.1, h₂.1, Clause.eval] using hcore.1 + · simp only [clause, CNF.evalOne_append, Bool.and_eq_true, core, + evalOne_disjunctionClauses_iff_canonical, forcePadding, + evalOne_forceFalseClauses_iff] at hτ + obtain ⟨hcore, h₂⟩ := hτ + simpa [padding, lift, Literal.eval, h₂.1, Clause.eval] using hcore.1 + · have hrest : rest = [] := by simpa using hc + subst rest + have hcore := (evalOne_disjunctionClauses_iff_canonical τ _ _ _ _ _ _ _ _).mp hτ + simpa [lift, Literal.eval, Clause.eval, or_assoc] using hcore.1 + +/-- The canonical extension satisfies a local gadget exactly when its source clause holds. -/ +theorem clause_complete (φ : CNF V) (σ : V → Bool) (i : ℕ) (c : Clause V) + (hi : φ[i]? = some c) (hc : c.length ≤ 3) : + (clause i c).evalOne (assignment φ σ) = c.eval σ := by + rw [Bool.eq_iff_iff] + constructor + · intro h + exact clause_sound i c hc (assignment φ σ) h + · intro h + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ + · simp at h + · simpa [clause, core, CNF.evalOne_append, evalOne_disjunctionClauses_iff_canonical, + forcePadding, evalOne_forceFalseClauses_iff, padding, lift, assignment, auxiliaryValue, + sourceValue, hi, aux, Clause.eval, Literal.eval] using h + · simpa [clause, core, CNF.evalOne_append, evalOne_disjunctionClauses_iff_canonical, + forcePadding, evalOne_forceFalseClauses_iff, padding, lift, assignment, auxiliaryValue, + sourceValue, hi, aux, Clause.eval, Literal.eval] using h + · have hrest : rest = [] := by simpa using hc + subst rest + simpa [clause, core, CNF.evalOne_append, evalOne_disjunctionClauses_iff_canonical, + padding, lift, assignment, auxiliaryValue, sourceValue, hi, aux, Clause.eval, + Literal.eval, or_assoc] using h + +/-- Restricting an exact-one output assignment recovers an ordinary satisfying assignment. -/ +theorem formula_sound (φ : CNF V) (hw : ∀ c ∈ φ, c.length ≤ 3) + (τ : Variable V → Bool) (hτ : (formula φ).evalOne τ = true) : + φ.eval (fun v => τ (Sum.inl v)) = true := by + rw [CNF.eval_eq_true_iff] + intro c hc + obtain ⟨i, hi, rfl⟩ := List.mem_iff_getElem.mp hc + apply clause_sound i _ (hw _ (List.getElem_mem hi)) τ + exact (CNF.evalOne_flatMap_eq_true_iff τ φ.zipIdx _).mp hτ (φ[i], i) + (List.mem_zipIdx_iff_getElem?.mpr (List.getElem?_eq_getElem hi)) + +/-- The canonical extension satisfies the output exactly when the source assignment does. -/ +theorem evalOne_formula_assignment (φ : CNF V) (σ : V → Bool) + (hw : ∀ c ∈ φ, c.length ≤ 3) : + (formula φ).evalOne (assignment φ σ) = φ.eval σ := by + rw [Bool.eq_iff_iff, formula, CNF.evalOne_flatMap_eq_true_iff, CNF.eval_eq_true_iff] + constructor + · intro h c hc + obtain ⟨i, hi, rfl⟩ := List.mem_iff_getElem.mp hc + have hm : (φ[i], i) ∈ φ.zipIdx := + List.mem_zipIdx_iff_getElem?.mpr (List.getElem?_eq_getElem hi) + have hlocal := h _ hm + rw [clause_complete φ σ i _ (List.getElem?_eq_getElem hi) + (hw _ (List.getElem_mem hi))] at hlocal + exact hlocal + · intro h ⟨c, i⟩ hi + have hic : φ[i]? = some c := List.mem_zipIdx_iff_getElem?.mp hi + have hc : c ∈ φ := List.fst_mem_of_mem_zipIdx hi + rw [clause_complete φ σ i c hic (hw c hc)] + exact h c hc + +/-- Width-three ordinary satisfiability is equivalent to exact-one output satisfiability. -/ +theorem oneSatisfiable_formula (φ : CNF V) (hw : ∀ c ∈ φ, c.length ≤ 3) : + (formula φ).OneSatisfiable ↔ φ.Satisfiable := by + constructor + · rintro ⟨τ, hτ⟩ + exact ⟨_, formula_sound φ hw τ hτ⟩ + · rintro ⟨σ, hσ⟩ + exact ⟨assignment φ σ, (evalOne_formula_assignment φ σ hw).trans hσ⟩ + +/-- Three literal occurrences per output clause give a constant-factor literal bound. -/ +theorem length_vars_formula_le (φ : CNF V) : (formula φ).vars.length ≤ 39 * φ.length := by + have h := CNF.length_vars_le_mul_length (fun c hc => (width_formula φ c hc).le) + simpa only [← Nat.mul_assoc] using h.trans (Nat.mul_le_mul_left 3 (length_formula_le φ)) + +/-- Every inherited variable occurs exactly as often in its clause replacement as in the +source clause. This includes repeated literals with either polarity. -/ +theorem count_inl_clause [DecidableEq V] (i : ℕ) (c : Clause V) (hc : c.length ≤ 3) (v : V) : + (clause i c).vars.count (Sum.inl v) = (CNF.vars [c]).count v := by + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ + · simp [clause, core, disjunctionClauses, forcePadding, forceFalseClauses, CNF.vars, + padding, aux, Std.Sat.Literal.negate] + · simp [clause, core, disjunctionClauses, forcePadding, forceFalseClauses, CNF.vars, + padding, lift, aux, Std.Sat.Literal.negate, List.count_cons] + · simp [clause, core, disjunctionClauses, forcePadding, forceFalseClauses, CNF.vars, + padding, lift, aux, Std.Sat.Literal.negate, List.count_cons] + · have hrest : rest = [] := by simpa using hc + subst rest + simp [clause, core, disjunctionClauses, CNF.vars, lift, aux, Std.Sat.Literal.negate, + List.count_cons] + +/-- The replacement uses auxiliaries only in its own clause index. -/ +theorem auxiliary_index {i j k : ℕ} {c : Clause V} + (h : Sum.inr (j, k) ∈ (clause i c).vars) : j = i := by + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ <;> + simp [clause, core, disjunctionClauses, forcePadding, forceFalseClauses, + padding, lift, aux, CNF.vars, Std.Sat.Literal.negate] at h <;> tauto + +/-- No local auxiliary exceeds three occurrences. -/ +theorem count_aux_clause [DecidableEq V] (i j k : ℕ) (c : Clause V) : + (clause i c).vars.count (Sum.inr (j, k)) ≤ 3 := by + by_cases hji : j = i + · subst j + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ <;> + simp only [vars, clause, core, disjunctionClauses, lift, aux, Std.Sat.Literal.negate, + padding, Bool.not_true, forcePadding, forceFalseClauses, mul_zero, add_zero, mul_one, + Nat.reduceAdd, Nat.reduceMul, List.cons_append, List.nil_append, List.flatten_cons, + List.flatten_nil, List.append_nil, List.map_cons, List.map_nil, ne_eq, reduceCtorEq, + not_false_eq_true, List.count_cons_of_ne, List.count_cons, + Sum.inr.injEq, Prod.mk.injEq, + List.count_nil, beq_iff_eq, true_and, zero_add] <;> + (by_cases hk : k < 17 + · interval_cases k <;> decide + · simp (discharger := omega) only [ite_eq_right, add_zero, Nat.zero_le]) + · rw [List.count_eq_zero.mpr (fun hm => hji (auxiliary_index hm))] + omega + +/-- Every replacement clause has three distinct variable names, even when source variables +are repeated. -/ +theorem nodup_clause (i : ℕ) (c : Clause V) : + ∀ d ∈ clause i c, (d.map Prod.fst).Nodup := by + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ <;> + simp [clause, core, disjunctionClauses, forcePadding, forceFalseClauses, + padding, lift, aux, Std.Sat.Literal.negate] + +/-- Taking variable occurrences commutes with concatenating clause replacements. -/ +theorem vars_formula (φ : CNF V) : + (formula φ).vars = φ.zipIdx.flatMap fun c => (clause c.2 c.1).vars := by + simp [formula, CNF.vars, List.flatMap_def, List.map_flatten, List.flatten_flatten, + List.map_map, Function.comp_def] + +/-- Source occurrence counts are preserved exactly by the whole replacement. -/ +theorem count_inl_formula [DecidableEq V] (φ : CNF V) + (hw : ∀ c ∈ φ, c.length ≤ 3) (v : V) : + (formula φ).vars.count (Sum.inl v) = φ.vars.count v := by + rw [vars_formula, List.count_flatMap] + have hlocal (c : Clause V × ℕ) (hc : c ∈ φ.zipIdx) : + (clause c.2 c.1).vars.count (Sum.inl v) = (CNF.vars [c.1]).count v := + count_inl_clause c.2 c.1 (hw _ (List.fst_mem_of_mem_zipIdx hc)) v + simp only [Function.comp_def] + rw [List.map_congr_left (fun c hc => hlocal c hc)] + simp only [CNF.vars, List.flatten_cons, List.flatten_nil, List.append_nil, + List.map_flatten, List.count_flatten, List.map_map, Function.comp_def] + simpa only [List.map_map, Function.comp_def] using + congrArg (fun cs : List (Clause V) => (cs.map fun c => (c.map Prod.fst).count v).sum) + (List.zipIdx_map_fst 0 φ) + +/-- Each auxiliary belongs to just one source clause, so the local degree bound is global. -/ +theorem count_aux_formula [DecidableEq V] (φ : CNF V) (i k : ℕ) : + (formula φ).vars.count (Sum.inr (i, k)) ≤ 3 := by + rw [vars_formula, List.count_flatMap] + have hlocal (c : Clause V × ℕ) : + (clause c.2 c.1).vars.count (Sum.inr (i, k)) ≤ if c.2 = i then 3 else 0 := by + split_ifs with hi + · exact count_aux_clause _ _ _ _ + · rw [List.count_eq_zero.mpr (fun hm => hi (auxiliary_index hm).symm)] + calc + _ ≤ (φ.zipIdx.map fun c => if c.2 = i then 3 else 0).sum := + List.sum_le_sum fun c _ => hlocal c + _ = ((φ.zipIdx.map Prod.snd).map fun j => if j = i then 3 else 0).sum := by + rw [List.map_map] + rfl + _ = (φ.zipIdx.map Prod.snd).count i * 3 := by + rw [List.sum_map_eq_nsmul_single i _ (fun _ h _ => by simp [h])] + simp + _ ≤ 3 := by + have h := List.nodup_iff_count_le_one.mp (List.nodup_zipIdx_map_snd φ) i + omega + +/-- A three-occurrence source produces a three-occurrence exact-one formula. -/ +theorem count_vars_formula_le [DecidableEq V] (φ : CNF V) + (hw : ∀ c ∈ φ, c.length ≤ 3) (ho : ∀ v, φ.vars.count v ≤ 3) + (v : Variable V) : (formula φ).vars.count v ≤ 3 := by + cases v with + | inl v => rw [count_inl_formula φ hw]; exact ho v + | inr v => exact count_aux_formula φ v.1 v.2 + +/-- Exactly the used source variables occur among the inherited output variables. -/ +theorem inl_mem_vars_formula (φ : CNF V) + (hw : ∀ c ∈ φ, c.length ≤ 3) (v : V) : + Sum.inl v ∈ (formula φ).vars ↔ v ∈ φ.vars := by + classical + rw [← List.count_pos_iff, count_inl_formula φ hw, List.count_pos_iff] + +/-- Two satisfying local assignments that agree on source variables agree everywhere used +by the replacement. This is the unique-extension property on the actual variable support. -/ +theorem clause_agree (i : ℕ) (c : Clause V) (σ τ : Variable V → Bool) + (hσ : (clause i c).evalOne σ = true) (hτ : (clause i c).evalOne τ = true) + (hs : ∀ l ∈ c, σ (Sum.inl l.1) = τ (Sum.inl l.1)) : + ∀ v ∈ (clause i c).vars, σ v = τ v := by + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ <;> + simp only [clause, core, CNF.evalOne_append, Bool.and_eq_true, + evalOne_disjunctionClauses_iff_canonical, forcePadding, + evalOne_forceFalseClauses_iff, padding, lift, Literal.eval_mk, + aux] at hσ hτ <;> + simp only [List.mem_cons, forall_eq_or_imp, List.not_mem_nil, forall_false] at hs <;> + simp only [vars, clause, core, disjunctionClauses, lift, padding, aux, + Std.Sat.Literal.negate, Bool.not_true, forcePadding, forceFalseClauses, + mul_zero, add_zero, mul_one, Nat.reduceAdd, Nat.reduceMul, List.cons_append, + List.nil_append, List.flatten_cons, List.flatten_nil, List.append_nil, + List.map_cons, List.map_nil, List.mem_cons, List.not_mem_nil, or_false, + forall_eq_or_imp, forall_eq] <;> + grind + +/-- Satisfying output assignments are determined exactly by their source restrictions. -/ +theorem assignments_agree_iff (φ : CNF V) (hw : ∀ c ∈ φ, c.length ≤ 3) + (σ τ : Variable V → Bool) (hσ : (formula φ).evalOne σ = true) + (hτ : (formula φ).evalOne τ = true) : + (∀ v ∈ φ.vars, σ (Sum.inl v) = τ (Sum.inl v)) ↔ + ∀ v ∈ (formula φ).vars, σ v = τ v := by + constructor + · intro hs v hv + rw [vars_formula] at hv + obtain ⟨⟨c, i⟩, hi, hv⟩ := List.mem_flatMap.mp hv + have hc := List.fst_mem_of_mem_zipIdx hi + exact clause_agree i c σ τ + ((CNF.evalOne_flatMap_eq_true_iff σ φ.zipIdx _).mp hσ _ hi) + ((CNF.evalOne_flatMap_eq_true_iff τ φ.zipIdx _).mp hτ _ hi) + (fun l hl => hs l.1 (CNF.mem_vars.mpr ⟨c, hc, l.2, hl⟩)) v hv + · intro hs v hv + exact hs _ ((inl_mem_vars_formula φ hw v).mpr hv) + +/-- The ordinary and exact-one counts coincide, on occurring variables on both sides. -/ +theorem oneModelCount_formula [DecidableEq V] (φ : CNF V) + (hw : ∀ c ∈ φ, c.length ≤ 3) : (formula φ).oneModelCount = φ.modelCount := by + let f (τ : Variable V → Bool) (v : φ.vars.toFinset) : Bool := τ (Sum.inl v) + change (formula φ).oneModelCount = + (Finset.univ.filter fun τ : φ.vars.toFinset → Bool => + φ.eval (φ.extendAssignment τ) = true).card + refine CNF.oneModelCount_eq_card_filter f + (fun τ => φ.eval (φ.extendAssignment τ) = true) (fun σ τ hσ hτ => ?_) fun τ => ?_ + · rw [← assignments_agree_iff φ hw σ τ hσ hτ] + exact ⟨fun h v hv => congrFun h ⟨v, List.mem_toFinset.mpr hv⟩, + fun h => funext fun v => h v (List.mem_toFinset.mp v.2)⟩ + · constructor + · intro hτ + refine ⟨assignment φ (φ.extendAssignment τ), ?_, funext fun v => ?_⟩ + · exact (evalOne_formula_assignment φ _ hw).trans hτ + · exact CNF.extendAssignment_of_mem τ (List.mem_toFinset.mp v.2) + · rintro ⟨σ, hσ, rfl⟩ + have hs := formula_sound φ hw σ hσ + exact (CNF.eval_congr (fun v hv => + CNF.extendAssignment_of_mem (f σ) hv)).trans hs + +end Cslib.Logic.PL.CNF.FromSAT diff --git a/Complexity/Logics/Propositional/CNF/ExactOne/Gadgets.lean b/Complexity/Logics/Propositional/CNF/ExactOne/Gadgets.lean new file mode 100644 index 000000000..8315fe3bd --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/ExactOne/Gadgets.lean @@ -0,0 +1,241 @@ +/- +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.ExactOne + +/-! +# Parsimonious exact-one clause gadgets + +The disjunction gadget is the four-clause construction of +[Hunt, Marathe, Radhakrishnan and Stearns, Theorem 3.8, Figure 3.4(a)] +[HuntMaratheRadhakrishnanStearns1998]. It admits exactly one auxiliary assignment for each +satisfying assignment of the three source literals. The positive constant gadget is their +Lemma 3.6; it forces one designated variable false and has a unique assignment to all four +variables. Attaching that constant to a positive ternary clause gives a parsimonious complement +constraint, as in their Figure 3.5. + +## Main definitions + +* `disjunctionClauses`: four exact-three clauses replacing one ordinary disjunction. +* `forceFalseClauses`: three positive clauses forcing a designated variable false. +* `complementClauses`: four positive clauses enforcing opposite terminal values. + +## Main results + +* `disjunctionHolds_iff`: disjunction correctness and the unique five auxiliary values. +* `evalOne_disjunctionClauses_iff`: the corresponding formula-level characterization. +* `evalOne_forceFalseClauses_iff`, `evalOne_complementClauses_iff`: unique constant and + complement extensions. + +## References + +* [Hunt, Marathe, Radhakrishnan and Stearns][HuntMaratheRadhakrishnanStearns1998], + Theorem 3.8, Figures 3.4–3.5 and Lemma 3.6. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.ExactOneGadget + +variable {V : Type*} + +/-- Exactly one of three Boolean values is true. -/ +def One (a b c : Bool) : Prop := [a, b, c].count true = 1 + +instance (a b c : Bool) : Decidable (One a b c) := + inferInstanceAs (Decidable ([a, b, c].count true = 1)) + +/-- The four scalar exact-one constraints in the parsimonious disjunction gadget. -/ +def DisjunctionHolds (p q r u v w t x : Bool) : Prop := + One p u v ∧ One (!q) u w ∧ One v w t ∧ One (!r) v x + +instance (p q r u v w t x : Bool) : Decidable (DisjunctionHolds p q r u v w t x) := + inferInstanceAs (Decidable (One p u v ∧ One (!q) u w ∧ One v w t ∧ One (!r) v x)) + +private theorem beq_true (b : Bool) : (b == true) = b := by cases b <;> rfl + +/-- The first auxiliary value of the disjunction gadget. -/ +def firstAux (p q : Bool) : Bool := !p && q + +/-- The second auxiliary value of the disjunction gadget. -/ +def secondAux (p q : Bool) : Bool := !p && !q + +/-- The third auxiliary value of the disjunction gadget. -/ +def thirdAux (p q : Bool) : Bool := p && q + +/-- The fourth auxiliary value of the disjunction gadget. -/ +def fourthAux (p q : Bool) : Bool := !(secondAux p q || thirdAux p q) + +/-- The fifth auxiliary value of the disjunction gadget. -/ +def fifthAux (p q r : Bool) : Bool := r && (p || q) + +/-- Every satisfying source triple has exactly the displayed auxiliary extension, and the +all-false source triple has none. -/ +theorem disjunctionHolds_iff (p q r u v w t x : Bool) : + DisjunctionHolds p q r u v w t x ↔ + (p || q || r) = true ∧ u = firstAux p q ∧ v = secondAux p q ∧ + w = thirdAux p q ∧ t = fourthAux p q ∧ x = fifthAux p q r := by + cases p <;> cases q <;> cases r <;> cases u <;> cases v <;> cases w <;> cases t <;> + cases x <;> decide + +/-- The canonical auxiliary values satisfy the gadget exactly on satisfying source triples. -/ +theorem disjunctionHolds_canonical_iff (p q r : Bool) : + DisjunctionHolds p q r (firstAux p q) (secondAux p q) (thirdAux p q) + (fourthAux p q) (fifthAux p q r) ↔ (p || q || r) = true := by + simp [disjunctionHolds_iff] + +/-- Replace a signed three-literal disjunction by four exact-three clauses. Auxiliary literals +are positive, while the second and third source literals are negated. -/ +def disjunctionClauses (p q r : Literal V) (u v w t x : V) : CNF V := + [[p, (u, true), (v, true)], + [Std.Sat.Literal.negate q, (u, true), (w, true)], + [(v, true), (w, true), (t, true)], + [Std.Sat.Literal.negate r, (v, true), (x, true)]] + +/-- A positive three-clause gadget with one true auxiliary and three false variables. The +first variable is the false terminal and has two internal occurrences. -/ +def forceFalseClauses (c d e f : V) : CNF V := + [[(c, true), (d, true), (e, true)], + [(c, true), (e, true), (f, true)], + [(d, true), (e, true), (f, true)]] + +/-- Opposite terminal values, enforced using a uniquely determined positive false gadget. -/ +def complementClauses (p q c d e f : V) : CNF V := + [(p, true), (q, true), (c, true)] :: forceFalseClauses c d e f + +/-- The fixed disjunction gadget with source variables `0, 1, 2` and auxiliaries `3` through +`7`. Its incidence drawing is independent of the literal polarities. -/ +def disjunction : CNF ℕ := disjunctionClauses (0, true) (1, true) (2, true) 3 4 5 6 7 + +/-- The fixed positive false gadget, with false terminal `0`. -/ +def forceFalse : CNF ℕ := forceFalseClauses 0 1 2 3 + +/-- The fixed positive complement gadget, with terminals `0, 1` and auxiliaries `2` through +`5`. -/ +def complement : CNF ℕ := complementClauses 0 1 2 3 4 5 + +/-- The scalar exact-one predicate is the evaluation of three literals. -/ +theorem evalOne_triple_iff (σ : V → Bool) (a b c : Literal V) : + Clause.evalOne σ [a, b, c] = true ↔ + One (Literal.eval σ a) (Literal.eval σ b) (Literal.eval σ c) := by + simp only [Clause.evalOne, List.countP_cons, List.countP_nil, beq_iff_eq] + cases Literal.eval σ a <;> cases Literal.eval σ b <;> cases Literal.eval σ c <;> + decide + +/-- Formula evaluation recovers the four scalar constraints. -/ +theorem evalOne_disjunctionClauses_iff (σ : V → Bool) (p q r : Literal V) + (u v w t x : V) : + CNF.evalOne σ (disjunctionClauses p q r u v w t x) = true ↔ + DisjunctionHolds (Literal.eval σ p) (Literal.eval σ q) (Literal.eval σ r) + (σ u) (σ v) (σ w) (σ t) (σ x) := by + simp only [disjunctionClauses, CNF.evalOne, List.all_cons, List.all_nil, + Bool.and_eq_true, and_true, evalOne_triple_iff, Literal.eval_negate, Literal.eval_mk, + beq_true, DisjunctionHolds] + +/-- The formula has precisely the canonical disjunction extension. -/ +theorem evalOne_disjunctionClauses_iff_canonical (σ : V → Bool) (p q r : Literal V) + (u v w t x : V) : + CNF.evalOne σ (disjunctionClauses p q r u v w t x) = true ↔ + (Literal.eval σ p || Literal.eval σ q || Literal.eval σ r) = true ∧ + σ u = firstAux (Literal.eval σ p) (Literal.eval σ q) ∧ + σ v = secondAux (Literal.eval σ p) (Literal.eval σ q) ∧ + σ w = thirdAux (Literal.eval σ p) (Literal.eval σ q) ∧ + σ t = fourthAux (Literal.eval σ p) (Literal.eval σ q) ∧ + σ x = fifthAux (Literal.eval σ p) (Literal.eval σ q) (Literal.eval σ r) := + (evalOne_disjunctionClauses_iff σ p q r u v w t x).trans + (disjunctionHolds_iff _ _ _ _ _ _ _ _) + +/-- The positive false gadget has exactly the values `(false, false, true, false)`. -/ +theorem evalOne_forceFalseClauses_iff (σ : V → Bool) (c d e f : V) : + CNF.evalOne σ (forceFalseClauses c d e f) = true ↔ + σ c = false ∧ σ d = false ∧ σ e = true ∧ σ f = false := by + simp only [forceFalseClauses, CNF.evalOne, List.all_cons, List.all_nil, + Bool.and_eq_true, and_true, evalOne_triple_iff, Literal.eval_mk, beq_true] + cases σ c <;> cases σ d <;> cases σ e <;> cases σ f <;> decide + +/-- The positive complement gadget forces opposite terminals and uniquely fixes every +auxiliary variable. -/ +theorem evalOne_complementClauses_iff (σ : V → Bool) (p q c d e f : V) : + CNF.evalOne σ (complementClauses p q c d e f) = true ↔ + σ p = !σ q ∧ σ c = false ∧ σ d = false ∧ σ e = true ∧ σ f = false := by + change (Clause.evalOne σ [(p, true), (q, true), (c, true)] && + CNF.evalOne σ (forceFalseClauses c d e f)) = true ↔ _ + rw [Bool.and_eq_true, evalOne_triple_iff, evalOne_forceFalseClauses_iff] + simp only [Literal.eval_mk, beq_true] + cases σ p <;> cases σ q <;> cases σ c <;> cases σ d <;> cases σ e <;> cases σ f <;> + decide + +/-- The disjunction gadget has four clauses. -/ +theorem length_disjunctionClauses (p q r : Literal V) (u v w t x : V) : + (disjunctionClauses p q r u v w t x).length = 4 := rfl + +/-- The positive false gadget has three clauses. -/ +theorem length_forceFalseClauses (c d e f : V) : + (forceFalseClauses c d e f).length = 3 := rfl + +/-- The positive complement gadget has four clauses. -/ +theorem length_complementClauses (p q c d e f : V) : + (complementClauses p q c d e f).length = 4 := rfl + +/-- The disjunction gadget has twelve literal occurrences. -/ +theorem length_vars_disjunctionClauses (p q r : Literal V) (u v w t x : V) : + (disjunctionClauses p q r u v w t x).vars.length = 12 := rfl + +/-- The positive false gadget has nine literal occurrences. -/ +theorem length_vars_forceFalseClauses (c d e f : V) : + (forceFalseClauses c d e f).vars.length = 9 := rfl + +/-- The positive complement gadget has twelve literal occurrences. -/ +theorem length_vars_complementClauses (p q c d e f : V) : + (complementClauses p q c d e f).vars.length = 12 := rfl + +/-- Every clause of a disjunction gadget is ternary. -/ +theorem width_disjunctionClauses (p q r : Literal V) (u v w t x : V) : + ∀ c ∈ disjunctionClauses p q r u v w t x, c.length = 3 := by + simp [disjunctionClauses] + +/-- Every clause of a false gadget is ternary. -/ +theorem width_forceFalseClauses (c d e f : V) : + ∀ a ∈ forceFalseClauses c d e f, a.length = 3 := by + simp [forceFalseClauses] + +/-- Every clause of a complement gadget is ternary. -/ +theorem width_complementClauses (p q c d e f : V) : + ∀ a ∈ complementClauses p q c d e f, a.length = 3 := by + simp [complementClauses, forceFalseClauses] + +/-- The false gadget contains only positive literals. -/ +theorem positive_forceFalseClauses (c d e f : V) : + ∀ a ∈ forceFalseClauses c d e f, ∀ l ∈ a, l.2 = true := by + simp [forceFalseClauses] + +/-- The complement gadget contains only positive literals. -/ +theorem positive_complementClauses (p q c d e f : V) : + ∀ a ∈ complementClauses p q c d e f, ∀ l ∈ a, l.2 = true := by + simp [complementClauses, forceFalseClauses] + +/-- Occurrence counts of every variable in the fixed disjunction gadget. -/ +theorem disjunction_occurrences : + disjunction.vars.count 0 = 1 ∧ disjunction.vars.count 1 = 1 ∧ + disjunction.vars.count 2 = 1 ∧ disjunction.vars.count 3 = 2 ∧ + disjunction.vars.count 4 = 3 ∧ disjunction.vars.count 5 = 2 ∧ + disjunction.vars.count 6 = 1 ∧ disjunction.vars.count 7 = 1 := by decide + +/-- The false port has degree two; no variable in the fixed false gadget exceeds degree three. -/ +theorem forceFalse_occurrences : + forceFalse.vars.count 0 = 2 ∧ forceFalse.vars.count 1 = 2 ∧ + forceFalse.vars.count 2 = 3 ∧ forceFalse.vars.count 3 = 2 := by decide + +/-- Each complement terminal has degree one, leaving its original incidence capacity intact. -/ +theorem complement_occurrences : + complement.vars.count 0 = 1 ∧ complement.vars.count 1 = 1 ∧ + complement.vars.count 2 = 3 ∧ complement.vars.count 3 = 2 ∧ + complement.vars.count 4 = 3 ∧ complement.vars.count 5 = 2 := by decide + +end Cslib.Logic.PL.CNF.ExactOneGadget diff --git a/Complexity/Logics/Propositional/CNF/ExactOne/ModelCount.lean b/Complexity/Logics/Propositional/CNF/ExactOne/ModelCount.lean new file mode 100644 index 000000000..99934f8d9 --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/ExactOne/ModelCount.lean @@ -0,0 +1,94 @@ +/- +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.ExactOne +public import Complexity.Logics.Propositional.CNF.Compact +public import Complexity.Logics.Propositional.CNF.ModelCount.Relabel + +/-! +# Counting exact-one satisfying assignments + +The exact-one count reuses `CNF.modelCount` through the ordinary CNF encoding, which preserves +both assignments and occurring variables. Variables outside the formula are not counted. + +## Main definitions + +* `CNF.oneModelCount`: the exact-one satisfying-assignment count. + +## Main results + +* `CNF.oneModelCount_eq_card_filter`: count through a finite assignment representation. +* `CNF.oneModelCount_relabel_of_injOn`: support-injective relabelling preserves the count. +* `CNF.oneModelCount_compact`: compact numbering preserves the count. + +## References + +These are original consequences of the ordinary CNF model-count API. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF + +variable {V W : Type*} [DecidableEq V] + +/-- The number of exact-one satisfying assignments on the occurring variables. -/ +def oneModelCount (φ : CNF V) : ℕ := φ.oneEncoding.modelCount + +/-- Count exact-one assignments through any faithful finite representation of them. -/ +theorem oneModelCount_eq_card_filter {ι : Type*} [Fintype ι] {φ : CNF V} + (f : (V → Bool) → ι) (P : ι → Prop) [DecidablePred P] + (hf : ∀ σ τ, φ.evalOne σ = true → φ.evalOne τ = true → + (f σ = f τ ↔ ∀ v ∈ φ.vars, σ v = τ v)) + (hP : ∀ i, P i ↔ ∃ σ, φ.evalOne σ = true ∧ f σ = i) : + φ.oneModelCount = (Finset.univ.filter P).card := by + apply modelCount_eq_card_filter f P + · intro σ τ hσ hτ + simpa only [mem_vars_oneEncoding] using hf σ τ + ((eval_oneEncoding σ φ) ▸ hσ) ((eval_oneEncoding τ φ) ▸ hτ) + · simpa only [eval_oneEncoding] using hP + +/-- An exact-one model exists exactly when the exact-one count is positive. -/ +theorem oneModelCount_pos_iff {φ : CNF V} : 0 < φ.oneModelCount ↔ φ.OneSatisfiable := by + rw [oneModelCount, modelCount_pos_iff, satisfiable_oneEncoding] + +/-- Support-injective relabelling preserves the number of exact-one models. -/ +theorem oneModelCount_relabel_of_injOn [DecidableEq W] {f : V → W} (φ : CNF V) + (hf : Set.InjOn f {v | v ∈ φ.vars}) : + (φ.relabel f).oneModelCount = φ.oneModelCount := by + rw [oneModelCount, oneModelCount, oneEncoding_relabel] + exact modelCount_relabel_of_injOn φ.oneEncoding + (fun _ ha _ hb h => hf (mem_vars_oneEncoding.mp ha) (mem_vars_oneEncoding.mp hb) h) + +/-- Injective relabelling preserves the number of exact-one models. -/ +theorem oneModelCount_relabel [DecidableEq W] {f : V → W} (hf : Function.Injective f) + (φ : CNF V) : (φ.relabel f).oneModelCount = φ.oneModelCount := + oneModelCount_relabel_of_injOn φ (fun _ _ _ _ h => hf h) + +/-- Compact variable numbering preserves the number of exact-one models. -/ +theorem oneModelCount_compact (φ : CNF V) : φ.compact.oneModelCount = φ.oneModelCount := + oneModelCount_relabel_of_injOn φ (fun _ ha _ _ h => (List.idxOf_inj ha).mp h) + +/-- Reading original names at compact positions preserves exact-one evaluation. -/ +theorem evalOne_compact (φ : CNF V) (σ : V → Bool) : + φ.compact.evalOne (fun i => φ.vars[i]?.elim false σ) = φ.evalOne σ := by + rw [compact, evalOne_relabel] + apply evalOne_congr + intro v hv + simp [List.getElem?_idxOf hv] + +/-- Compact variable numbering preserves exact-one satisfiability. -/ +theorem oneSatisfiable_compact (φ : CNF V) : φ.compact.OneSatisfiable ↔ φ.OneSatisfiable := by + constructor + · rintro ⟨σ, hσ⟩ + exact ⟨σ ∘ φ.vars.idxOf, by simpa only [compact, evalOne_relabel] using hσ⟩ + · rintro ⟨σ, hσ⟩ + exact ⟨fun i => φ.vars[i]?.elim false σ, (evalOne_compact φ σ).trans hσ⟩ + +end Cslib.Logic.PL.CNF diff --git a/Complexity/Logics/Propositional/CNF/ExactOne/PolyTime.lean b/Complexity/Logics/Propositional/CNF/ExactOne/PolyTime.lean new file mode 100644 index 000000000..7dd201c67 --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/ExactOne/PolyTime.lean @@ -0,0 +1,247 @@ +/- +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.ExactOne +public import Complexity.Logics.Propositional.CNF.ExactOne.Compile +public import Complexity.Logics.Propositional.CNF.PolyTime +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Sum +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Sum + +/-! +# Polynomial-time exact-one encoding + +The ordinary CNF encoding of exact-one semantics enumerates pairs of literal positions. Its +construction is polynomial-time under the existing list and literal encodings. This is a +semantic bridge, separate from the positive exact-three reduction. + +## Main results + +* `Clause.computableInPolyTime_oneEncoding`: encode one exact-one clause. +* `CNF.computableInPolyTime_oneEncoding`: encode an exact-one formula. + +## References + +These are original computational certificates for the existing positional encoding. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL + +open Turing.MultiTapeTM + +variable {V : Type} + +/-- Enumerate each later literal position after its first position. -/ +theorem Clause.atMostOneOccurrences_eq_flatMap (c : Clause V) : + c.atMostOneOccurrences = c.zipIdx.flatMap fun p => + (c.drop (p.2 + 1)).map fun l => [Std.Sat.Literal.negate p.1, Std.Sat.Literal.negate l] := by + induction c with + | nil => rfl + | cons l c ih => + simp only [Clause.atMostOneOccurrences, List.zipIdx_cons', List.flatMap_cons, + List.flatMap_map, List.drop_succ_cons, List.drop_zero] + rw [ih] + rfl + +attribute [local fun_prop] ComputableInPolyTime.tidy + +/-- The positional pair clauses can be emitted in polynomial time. -/ +@[fun_prop] +theorem Clause.computableInPolyTime_atMostOneOccurrences (enc : V ↪ List Bool) : + ComputableInPolyTime (Clause.atMostOneOccurrences (V := V)) + (encList (encPair enc encBool)) (encList (encList (encPair enc encBool))) := by + have he : (Clause.atMostOneOccurrences (V := V)) = fun c => + c.zipIdx.flatMap fun p => (c.drop (p.2 + 1)).map fun l => + [Std.Sat.Literal.negate p.1, Std.Sat.Literal.negate l] := + funext Clause.atMostOneOccurrences_eq_flatMap + rw [he] + refine .of_tidy ?_ + fun_prop + +/-- Exact-one clause encoding is polynomial-time for any fixed variable encoding. -/ +@[fun_prop] +theorem Clause.computableInPolyTime_oneEncoding (enc : V ↪ List Bool) : + ComputableInPolyTime (Clause.oneEncoding (V := V)) + (encList (encPair enc encBool)) (encList (encList (encPair enc encBool))) := by + unfold Clause.oneEncoding + fun_prop + +/-- Exact-one formula encoding is polynomial-time for any fixed variable encoding. -/ +@[fun_prop] +theorem CNF.computableInPolyTime_oneEncoding (enc : V ↪ List Bool) : + ComputableInPolyTime (CNF.oneEncoding (V := V)) + (encList (encList (encPair enc encBool))) + (encList (encList (encPair enc encBool))) := by + refine .of_tidy ?_ + unfold CNF.oneEncoding + fun_prop + +end Cslib.Logic.PL + +namespace Cslib.Logic.PL.CNF.FromSAT + +open Turing.MultiTapeTM + +variable {V : Type} + +/-- Source labels retain their encoding; local clause and slot indices are unary. -/ +def encVariable (enc : V ↪ List Bool) : Variable V ↪ List Bool := + encSum enc (encPair encUnary encUnary) + +private def selectedLiteral (i k : ℕ) (c : Clause V) : Literal (Variable V) := + if k < c.length then (c.map lift).getD k (padding 0 0) else padding i k + +private theorem clause_eq_selected (i : ℕ) (c : Clause V) : + clause i c = core i (selectedLiteral i 0 c) (selectedLiteral i 1 c) + (selectedLiteral i 2 c) ++ + (if c.length < 1 then forcePadding i 0 else []) ++ + (if c.length < 2 then forcePadding i 1 else []) ++ + (if c.length < 3 then forcePadding i 2 else []) := by + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ <;> + simp [clause, selectedLiteral, List.getD] + +@[fun_prop] +private theorem computableInPolyTime_selectedLiteral (enc : V ↪ List Bool) (k : ℕ) : + ComputableTidilyInPolyTime (fun p : ℕ × Clause V => selectedLiteral p.1 k p.2) + (encPair encUnary (encList (encPair enc encBool))) + (encPair (encVariable enc) encBool) := by + have hm : ComputableTidilyInPolyTime (fun p : ℕ × Clause V => p.2.map lift) + (encPair encUnary (encList (encPair enc encBool))) + (encList (encPair (encVariable enc) encBool)) := by + unfold lift encVariable + fun_prop + have hl : ComputableTidilyInPolyTime (fun p : ℕ × Clause V => p.2.length) + (encPair encUnary (encList (encPair enc encBool))) encUnary := by fun_prop + have hg : ComputableTidilyInPolyTime + (fun p : ℕ × Clause V => (p.2.map lift).getD k (padding 0 0)) + (encPair encUnary (encList (encPair enc encBool))) + (encPair (encVariable enc) encBool) := + (ComputableTidilyInPolyTime.listGetD (encA := encPair (encVariable enc) encBool) + (encL := encList (encPair (encVariable enc) encBool)) (padding 0 0)).comp + (hm.prod (ComputableTidilyInPolyTime.const k)) + have hp : ComputableTidilyInPolyTime (fun p : ℕ × Clause V => padding p.1 k) + (encPair encUnary (encList (encPair enc encBool))) + (encPair (encVariable enc) encBool) := by + unfold padding aux encVariable + fun_prop + unfold selectedLiteral + exact ComputableTidilyInPolyTime.ite_decide + (ComputableTidilyInPolyTime.unary_lt' (ComputableTidilyInPolyTime.const k) hl) hg hp + +@[fun_prop] +private theorem computableInPolyTime_core (enc : V ↪ List Bool) : + ComputableTidilyInPolyTime + (fun p : ℕ × Literal (Variable V) × Literal (Variable V) × Literal (Variable V) => + core p.1 p.2.1 p.2.2.1 p.2.2.2) + (encPair encUnary (encPair (encPair (encVariable enc) encBool) + (encPair (encPair (encVariable enc) encBool) (encPair (encVariable enc) encBool)))) + (encList (encList (encPair (encVariable enc) encBool))) := by + unfold core aux encVariable ExactOneGadget.disjunctionClauses Std.Sat.Literal.negate + fun_prop + +@[fun_prop] +private theorem computableInPolyTime_forcePadding (enc : V ↪ List Bool) (k : ℕ) : + ComputableTidilyInPolyTime (fun i : ℕ => forcePadding (V := V) i k) encUnary + (encList (encList (encPair (encVariable enc) encBool))) := by + unfold forcePadding aux encVariable ExactOneGadget.forceFalseClauses + fun_prop + +/-- One ordinary-clause replacement is native polynomial-time. -/ +@[fun_prop] +theorem computableInPolyTime_clause (enc : V ↪ List Bool) : + ComputableInPolyTime (fun p : ℕ × Clause V => clause p.1 p.2) + (encPair encUnary (encList (encPair enc encBool))) + (encList (encList (encPair (encVariable enc) encBool))) := by + refine .of_tidy ?_ + have hs : (fun p : ℕ × Clause V => clause p.1 p.2) = fun p => + core p.1 (selectedLiteral p.1 0 p.2) (selectedLiteral p.1 1 p.2) + (selectedLiteral p.1 2 p.2) ++ + (if p.2.length < 1 then forcePadding p.1 0 else []) ++ + (if p.2.length < 2 then forcePadding p.1 1 else []) ++ + (if p.2.length < 3 then forcePadding p.1 2 else []) := + funext fun p => clause_eq_selected p.1 p.2 + rw [hs] + fun_prop + +/-- The ordinary clause replacements are polynomial-time under the original label encoding. -/ +@[fun_prop] +theorem computableInPolyTime_formula (enc : V ↪ List Bool) : + ComputableInPolyTime (formula (V := V)) + (encList (encList (encPair enc encBool))) + (encList (encList (encPair (encVariable enc) encBool))) := by + have hc := (computableInPolyTime_clause enc).tidy + refine .of_tidy ?_ + unfold formula + fun_prop + +end Cslib.Logic.PL.CNF.FromSAT + +namespace Cslib.Logic.PL.CNF.PositiveExactOne + +open Turing.MultiTapeTM + +variable {V : Type} + +/-- Source labels retain their encoding; clause, literal, and auxiliary positions are unary. -/ +def encVariable (enc : V ↪ List Bool) : Variable V ↪ List Bool := + encSum enc (encPair (encPair encUnary encUnary) encUnary) + +/-- Eliminating all negative occurrences is native polynomial-time. -/ +@[fun_prop] +theorem computableInPolyTime_formula (enc : V ↪ List Bool) : + ComputableInPolyTime (formula (V := V)) + (encList (encList (encPair enc encBool))) + (encList (encList (encPair (encVariable enc) encBool))) := by + refine .of_tidy ?_ + unfold formula clauseClauses complementBlock positiveLiteral auxiliary encVariable + ExactOneGadget.complementClauses ExactOneGadget.forceFalseClauses + fun_prop + +end Cslib.Logic.PL.CNF.PositiveExactOne + +namespace Cslib.Logic.PL.CNF.PositiveExactOneCompile + +open Turing.MultiTapeTM + +variable {V : Type} + +/-- The two local auxiliary families use unary indices and retain the source label encoding. -/ +def encVariable (enc : V ↪ List Bool) : Variable V ↪ List Bool := + PositiveExactOne.encVariable (FromSAT.encVariable enc) + +/-- Composing the two local replacements is native polynomial-time. -/ +@[fun_prop] +theorem computableInPolyTime_rawFormula (enc : V ↪ List Bool) : + ComputableInPolyTime (rawFormula (V := V)) + (encList (encList (encPair enc encBool))) + (encList (encList (encPair (encVariable enc) encBool))) := + (PositiveExactOne.computableInPolyTime_formula (FromSAT.encVariable enc)).comp + (FromSAT.computableInPolyTime_formula enc) + +/-- Compact output labels are computed as bounded unary ranks, then encoded in binary. -/ +@[fun_prop] +theorem computableInPolyTime_formula [DecidableEq V] (enc : V ↪ List Bool) : + ComputableInPolyTime (formula (V := V)) + (encList (encList (encPair enc encBool))) + (encList (encList (encPair encNat encBool))) := by + have hu := (CNF.isPolyTime_compact (encVariable enc)).comp + (computableInPolyTime_rawFormula enc) + 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] using hr + +end Cslib.Logic.PL.CNF.PositiveExactOneCompile diff --git a/Complexity/Logics/Propositional/CNF/ExactOne/Positive.lean b/Complexity/Logics/Propositional/CNF/ExactOne/Positive.lean new file mode 100644 index 000000000..d950494f8 --- /dev/null +++ b/Complexity/Logics/Propositional/CNF/ExactOne/Positive.lean @@ -0,0 +1,633 @@ +/- +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.ExactOne.Gadgets +public import Complexity.Logics.Propositional.CNF.ExactOne.ModelCount +import Mathlib.Algebra.Order.BigOperators.Group.List +import Mathlib.Algebra.Order.Ring.Nat +import Mathlib.Data.List.Sigma +import Mathlib.Tactic.IntervalCases +import Mathlib.Algebra.BigOperators.Ring.List + +/-! +# Parsimonious elimination of negative exact-one literals + +Every negative literal occurrence receives its own positive proxy. A positive complement gadget +forces the proxy to be the opposite of the original variable, with a unique auxiliary extension. +Each original variable keeps exactly its original occurrence count. A clause of width at most +three yields at most thirteen clauses, and all newly added clauses are positive triples. + +The construction is [Hunt, Marathe, Radhakrishnan and Stearns, Theorem 3.8, Figure 3.5] +[HuntMaratheRadhakrishnanStearns1998]. The occurrence indexing and quantitative accounting are +original. + +## Main definitions + +* `Variable`, `formula`: the occurrence-indexed positive compiler. +* `extend`: its canonical assignment extension. + +## Main results + +* `positive_formula`: every emitted literal is positive. +* `length_formula_le`: a constant-factor clause bound on bounded-width inputs. +* `oneModelCount_formula`, `assignments_agree_iff`: count preservation and unique extensions. +* `count_inl_formula`, `count_vars_formula_le`: inherited occurrences are preserved exactly, + and the occurrence-three restriction is preserved globally. +* `nodup_formula`: distinct variables within clauses remain distinct. + +## References + +* [Hunt, Marathe, Radhakrishnan and Stearns][HuntMaratheRadhakrishnanStearns1998], + Theorem 3.8, Figure 3.5 and Lemma 3.6. +-/ + +@[expose] public section + +namespace Cslib.Logic.PL.CNF.PositiveExactOne + +open ExactOneGadget + +variable {V : Type*} + +/-- Source variables and five private auxiliary slots for each negative occurrence. -/ +abbrev Variable (V : Type*) := Sum V ((ℕ × ℕ) × ℕ) + +/-- An auxiliary slot at one clause and literal position. Slot zero is the positive proxy. -/ +def auxiliary (i j k : ℕ) : Variable V := .inr ((i, j), k) + +/-- Replace a negative literal by its private positive proxy. -/ +def positiveLiteral (i j : ℕ) (l : Literal V) : Literal (Variable V) := + (if l.2 then .inl l.1 else auxiliary i j 0, true) + +/-- The positive complement constraints attached to one negative occurrence. -/ +def complementBlock (i j : ℕ) (l : Literal V) : CNF (Variable V) := + if l.2 then [] else + complementClauses (.inl l.1) (auxiliary i j 0) (auxiliary i j 1) + (auxiliary i j 2) (auxiliary i j 3) (auxiliary i j 4) + +/-- One source clause and the positive complement constraints for its negative occurrences. -/ +def clauseClauses (i : ℕ) (c : Clause V) : CNF (Variable V) := + [c.zipIdx.map fun (l, j) => positiveLiteral i j l] ++ + c.zipIdx.flatMap fun (l, j) => complementBlock i j l + +/-- Replace all negative literal occurrences by positive proxies and complement constraints. -/ +def formula (φ : CNF V) : CNF (Variable V) := + φ.zipIdx.flatMap fun (c, i) => clauseClauses i c + +/-- Extend a source assignment: proxies complement the source variable and the remaining +slots have the unique values of the false gadget. Unused slots are immaterial. -/ +def extend (φ : CNF V) (σ : V → Bool) : Variable V → Bool + | .inl v => σ v + | .inr ((i, j), k) => + if k = 0 then + (((φ[i]?).bind fun c => c[j]?).map fun l => !σ l.1).getD false + else k == 3 + +/-- Canonical proxy values at an existing source occurrence. -/ +theorem extend_auxiliary_zero (φ : CNF V) (σ : V → Bool) {i j : ℕ} + {c : Clause V} {l : Literal V} (hc : φ[i]? = some c) (hl : c[j]? = some l) : + extend φ σ (auxiliary i j 0) = !σ l.1 := by + simp [extend, auxiliary, hc, hl] + +/-- Canonical values of the constant gadget's four slots. -/ +theorem extend_auxiliary_constants (φ : CNF V) (σ : V → Bool) (i j : ℕ) : + extend φ σ (auxiliary i j 1) = false ∧ + extend φ σ (auxiliary i j 2) = false ∧ + extend φ σ (auxiliary i j 3) = true ∧ + extend φ σ (auxiliary i j 4) = false := by + simp [extend, auxiliary] + +/-- A negative occurrence's complement constraints hold under the canonical extension. -/ +theorem evalOne_complementBlock_extend (φ : CNF V) (σ : V → Bool) {i j : ℕ} + {c : Clause V} {l : Literal V} (hc : φ[i]? = some c) (hl : c[j]? = some l) : + CNF.evalOne (extend φ σ) (complementBlock i j l) = true := by + unfold complementBlock + split + · rfl + · rw [evalOne_complementClauses_iff] + have hy := extend_auxiliary_zero φ σ hc hl + have hconst := extend_auxiliary_constants φ σ i j + simp only [extend] at * + exact ⟨by rw [hy, Bool.not_not], hconst⟩ + +/-- If every complement block holds, the inherited positive clause has the original +exact-one value under the assignment read from source variables. -/ +theorem evalOne_inheritedClause (τ : Variable V → Bool) (i : ℕ) (c : Clause V) + (hcomp : ∀ a ∈ c.zipIdx, CNF.evalOne τ (complementBlock i a.2 a.1) = true) : + Clause.evalOne τ (c.zipIdx.map fun (l, j) => positiveLiteral i j l) = + Clause.evalOne (fun v => τ (.inl v)) c := by + have heval (a : Literal V × ℕ) (ha : a ∈ c.zipIdx) : + Literal.eval τ (positiveLiteral i a.2 a.1) = + Literal.eval (fun v => τ (.inl v)) a.1 := by + rcases a with ⟨⟨v, b⟩, j⟩ + cases b with + | true => simp [positiveLiteral, Literal.eval] + | false => + have hh := (evalOne_complementClauses_iff τ (.inl v) (auxiliary i j 0) + (auxiliary i j 1) (auxiliary i j 2) (auxiliary i j 3) (auxiliary i j 4)).mp + (hcomp ((v, false), j) ha) + have hy := congrArg Bool.not hh.1 + simpa [positiveLiteral, Literal.eval] using hy.symm + unfold Clause.evalOne + congr 1 + rw [List.countP_map] + calc + _ = c.zipIdx.countP (fun a => Literal.eval (fun v => τ (.inl v)) a.1) := + List.countP_congr (fun a ha => by + change Literal.eval τ (positiveLiteral i a.2 a.1) = true ↔ _ + rw [heval a ha]) + _ = c.countP (Literal.eval (fun v => τ (.inl v))) := by + have h := congrArg (List.countP (Literal.eval (fun v => τ (.inl v)))) + (by simp only [List.zipIdx_map_fst] : c.zipIdx.map Prod.fst = c) + simpa only [List.countP_map, Function.comp_def] using h + +/-- A compiled clause is exactly the source clause together with its complement constraints. -/ +theorem evalOne_clauseClauses_iff (τ : Variable V → Bool) (i : ℕ) (c : Clause V) : + CNF.evalOne τ (clauseClauses i c) = true ↔ + Clause.evalOne (fun v => τ (.inl v)) c = true ∧ + ∀ a ∈ c.zipIdx, CNF.evalOne τ (complementBlock i a.2 a.1) = true := by + simp only [clauseClauses, CNF.evalOne_append, Bool.and_eq_true, + CNF.evalOne_cons, CNF.evalOne_nil, Bool.and_true, CNF.evalOne_flatMap_eq_true_iff] + constructor + · rintro ⟨hmain, hcomp⟩ + exact ⟨(evalOne_inheritedClause τ i c hcomp) ▸ hmain, hcomp⟩ + · rintro ⟨hmain, hcomp⟩ + exact ⟨(evalOne_inheritedClause τ i c hcomp).symm ▸ hmain, hcomp⟩ + +/-- A satisfying positive output assignment restricts to a satisfying source assignment. -/ +theorem evalOne_of_formula (φ : CNF V) (τ : Variable V → Bool) + (hτ : CNF.evalOne τ (formula φ) = true) : + CNF.evalOne (fun v => τ (.inl v)) φ = true := by + rw [CNF.evalOne_eq_true_iff] + intro c hc + obtain ⟨i, hi, rfl⟩ := List.mem_iff_getElem.mp hc + have hzip : (φ[i], i) ∈ φ.zipIdx := + List.mem_zipIdx_iff_getElem?.mpr (List.getElem?_eq_getElem hi) + have hh := (CNF.evalOne_flatMap_eq_true_iff τ φ.zipIdx + (fun (c, i) => clauseClauses i c)).mp hτ (φ[i], i) hzip + exact (evalOne_clauseClauses_iff τ i φ[i]).mp hh |>.1 + +/-- Canonical extension preserves the exact-one value of the whole source formula. -/ +theorem evalOne_formula_extend (φ : CNF V) (σ : V → Bool) : + CNF.evalOne (extend φ σ) (formula φ) = CNF.evalOne σ φ := by + rw [Bool.eq_iff_iff] + constructor + · exact evalOne_of_formula φ (extend φ σ) + · intro hσ + rw [formula, CNF.evalOne_flatMap_eq_true_iff] + intro a ha + rcases a with ⟨c, i⟩ + rw [evalOne_clauseClauses_iff] + refine ⟨(CNF.evalOne_eq_true_iff σ φ).mp hσ c + (List.fst_mem_of_mem_zipIdx ha), ?_⟩ + intro a ha' + rcases a with ⟨l, j⟩ + exact evalOne_complementBlock_extend φ σ + (List.mem_zipIdx_iff_getElem?.mp ha) (List.mem_zipIdx_iff_getElem?.mp ha') + +/-- The positive output is exact-one satisfiable exactly when the source is. -/ +theorem oneSatisfiable_formula_iff (φ : CNF V) : + (formula φ).OneSatisfiable ↔ φ.OneSatisfiable := by + constructor + · rintro ⟨τ, hτ⟩ + exact ⟨fun v => τ (.inl v), evalOne_of_formula φ τ hτ⟩ + · rintro ⟨σ, hσ⟩ + exact ⟨extend φ σ, (evalOne_formula_extend φ σ).trans hσ⟩ + +/-- Every negative-occurrence complement block is satisfied by a satisfying output assignment. -/ +theorem evalOne_complementBlock_of_formula (φ : CNF V) (τ : Variable V → Bool) + (hτ : CNF.evalOne τ (formula φ) = true) {i j : ℕ} {c : Clause V} {l : Literal V} + (hc : φ[i]? = some c) (hl : c[j]? = some l) : + CNF.evalOne τ (complementBlock i j l) = true := by + have hblock := (CNF.evalOne_flatMap_eq_true_iff τ φ.zipIdx + (fun (c, i) => clauseClauses i c)).mp hτ (c, i) + (List.mem_zipIdx_iff_getElem?.mpr hc) + exact (evalOne_clauseClauses_iff τ i c).mp hblock |>.2 (l, j) + (List.mem_zipIdx_iff_getElem?.mpr hl) + +/-- Every source variable remains present, even when all its occurrences are negative. -/ +theorem sourceVariable_mem_formula {φ : CNF V} {v : V} (hv : v ∈ φ.vars) : + Sum.inl v ∈ (formula φ).vars := by + obtain ⟨c, hc, b, hl⟩ := CNF.mem_vars.mp hv + obtain ⟨i, hi, rfl⟩ := List.mem_iff_getElem.mp hc + obtain ⟨j, hj, hlj⟩ := List.mem_iff_getElem.mp hl + have hci : (φ[i], i) ∈ φ.zipIdx := + List.mem_zipIdx_iff_getElem?.mpr (List.getElem?_eq_getElem hi) + have hlj' : ((v, b), j) ∈ φ[i].zipIdx := + List.mem_zipIdx_iff_getElem?.mpr (by rw [List.getElem?_eq_getElem hj, hlj]) + apply CNF.mem_vars.mpr + cases b with + | true => + refine ⟨φ[i].zipIdx.map (fun (l, j) => positiveLiteral i j l), ?_, true, ?_⟩ + · exact List.mem_flatMap.mpr ⟨(φ[i], i), hci, + List.mem_append.mpr (Or.inl (List.mem_singleton_self _))⟩ + · exact List.mem_map.mpr ⟨((v, true), j), hlj', rfl⟩ + | false => + refine ⟨[(.inl v, true), (auxiliary i j 0, true), (auxiliary i j 1, true)], ?_, + true, List.mem_cons_self⟩ + apply List.mem_flatMap.mpr + refine ⟨(φ[i], i), hci, List.mem_append.mpr (Or.inr ?_)⟩ + exact List.mem_flatMap.mpr ⟨((v, false), j), hlj', List.mem_cons_self⟩ + +/-- Satisfying complement blocks with equal source values agree on all of their variables. -/ +theorem complementBlock_assignments_agree (σ τ : Variable V → Bool) (i j : ℕ) + (l : Literal V) (hσ : CNF.evalOne σ (complementBlock i j l) = true) + (hτ : CNF.evalOne τ (complementBlock i j l) = true) + (hsource : σ (.inl l.1) = τ (.inl l.1)) : + ∀ v ∈ (complementBlock i j l).vars, σ v = τ v := by + rcases l with ⟨a, b⟩ + cases b with + | true => simp [complementBlock, CNF.vars] + | false => + have hs := (evalOne_complementClauses_iff σ _ _ _ _ _ _).mp hσ + have ht := (evalOne_complementClauses_iff τ _ _ _ _ _ _).mp hτ + have hy : σ (auxiliary i j 0) = τ (auxiliary i j 0) := by + have he := hs.1.symm.trans (hsource.trans ht.1) + have he' := congrArg Bool.not he + simpa using he' + intro v hv + simp only [complementBlock, Bool.false_eq_true, ↓reduceIte, complementClauses, + forceFalseClauses, CNF.vars, List.flatten_cons, List.flatten_nil, List.cons_append, + List.nil_append, List.map_cons, List.map_nil, List.mem_cons, List.not_mem_nil, + or_false] at hv + rcases hv with rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl + all_goals first + | exact hsource + | exact hy + | exact hs.2.1.trans ht.2.1.symm + | exact hs.2.2.1.trans ht.2.2.1.symm + | exact hs.2.2.2.1.trans ht.2.2.2.1.symm + | exact hs.2.2.2.2.trans ht.2.2.2.2.symm + +/-- Satisfying output assignments agree exactly when their source restrictions agree. -/ +theorem assignments_agree_iff {φ : CNF V} {σ τ : Variable V → Bool} + (hσ : CNF.evalOne σ (formula φ) = true) (hτ : CNF.evalOne τ (formula φ) = true) : + (∀ v ∈ (formula φ).vars, σ v = τ v) ↔ + ∀ v ∈ φ.vars, σ (.inl v) = τ (.inl v) := by + constructor + · intro h v hv + exact h (.inl v) (sourceVariable_mem_formula hv) + · intro h v hv + obtain ⟨c, hc, b, hl⟩ := CNF.mem_vars.mp hv + obtain ⟨⟨a, i⟩, ha, hc⟩ := List.mem_flatMap.mp hc + have hσblock := (CNF.evalOne_flatMap_eq_true_iff σ φ.zipIdx + (fun (c, i) => clauseClauses i c)).mp hσ (a, i) ha + have hτblock := (CNF.evalOne_flatMap_eq_true_iff τ φ.zipIdx + (fun (c, i) => clauseClauses i c)).mp hτ (a, i) ha + have hcompσ := (evalOne_clauseClauses_iff σ i a).mp hσblock |>.2 + have hcompτ := (evalOne_clauseClauses_iff τ i a).mp hτblock |>.2 + have hsource (l : Literal V) (j : ℕ) (hl : (l, j) ∈ a.zipIdx) : + σ (.inl l.1) = τ (.inl l.1) := by + apply h l.1 + exact CNF.mem_vars.mpr ⟨a, List.fst_mem_of_mem_zipIdx ha, l.2, + List.fst_mem_of_mem_zipIdx hl⟩ + rcases List.mem_append.mp hc with hc | hc + · obtain rfl := List.mem_singleton.mp hc + obtain ⟨⟨l, j⟩, hlj, he⟩ := List.mem_map.mp hl + have hv : v = (positiveLiteral i j l).1 := (congrArg Prod.fst he).symm + rw [hv] + rcases l with ⟨w, b⟩ + cases b with + | true => exact hsource (w, true) j hlj + | false => + exact complementBlock_assignments_agree σ τ i j (w, false) + (hcompσ ((w, false), j) hlj) (hcompτ ((w, false), j) hlj) + (hsource (w, false) j hlj) (auxiliary i j 0) (by + simp [complementBlock, complementClauses, forceFalseClauses, CNF.vars]) + · obtain ⟨⟨l, j⟩, hlj, hc⟩ := List.mem_flatMap.mp hc + exact complementBlock_assignments_agree σ τ i j l + (hcompσ (l, j) hlj) (hcompτ (l, j) hlj) (hsource l j hlj) v + (CNF.mem_vars.mpr ⟨c, hc, b, hl⟩) + +/-- Polarity elimination preserves the number of satisfying assignments on occurring variables. -/ +theorem oneModelCount_formula [DecidableEq V] (φ : CNF V) : + (formula φ).oneModelCount = φ.oneModelCount := by + classical + let P (a : φ.vars.toFinset → Bool) : Prop := + ∃ σ : V → Bool, φ.evalOne σ = true ∧ ∀ v : φ.vars.toFinset, σ v = a v + have hsource := CNF.oneModelCount_eq_card_filter + (φ := φ) (fun σ (v : φ.vars.toFinset) => σ v) P + (fun σ τ _ _ => by simp [funext_iff, List.mem_toFinset]) + (fun a => by simp [P, funext_iff]) + have houtput := CNF.oneModelCount_eq_card_filter + (φ := formula φ) (fun τ (v : φ.vars.toFinset) => τ (.inl v)) P + (fun σ τ hσ hτ => by + rw [funext_iff] + constructor + · intro hh + apply (assignments_agree_iff hσ hτ).mpr + intro v hv + exact hh ⟨v, List.mem_toFinset.mpr hv⟩ + · intro hh v + exact (assignments_agree_iff hσ hτ).mp hh v (List.mem_toFinset.mp v.property)) + (fun a => by + constructor + · rintro ⟨σ, hσ, ha⟩ + refine ⟨extend φ σ, (evalOne_formula_extend φ σ).trans hσ, ?_⟩ + funext v + exact ha v + · rintro ⟨τ, hτ, ha⟩ + exact ⟨fun v => τ (.inl v), evalOne_of_formula φ τ hτ, fun v => congrFun ha v⟩) + exact houtput.trans hsource.symm + +/-- A positive literal introduces no complement clauses. -/ +theorem complementBlock_positive (i j : ℕ) (v : V) : + complementBlock i j (v, true) = [] := rfl + +/-- A negative literal introduces four clauses. -/ +theorem length_complementBlock_negative (i j : ℕ) (v : V) : + (complementBlock i j (v, false)).length = 4 := rfl + +/-- An individual complement block has at most four clauses. -/ +theorem length_complementBlock_le (i j : ℕ) (l : Literal V) : + (complementBlock i j l).length ≤ 4 := by + cases l with + | mk v b => cases b <;> simp [complementBlock, complementClauses, forceFalseClauses] + +/-- An individual source clause contributes at most four clauses per literal, plus itself. -/ +theorem length_clauseClauses_le (i : ℕ) (c : Clause V) : + (clauseClauses i c).length ≤ 1 + 4 * c.length := by + simp only [clauseClauses, List.length_append, List.length_cons, List.length_nil, + List.length_flatMap] + have h : (c.zipIdx.map fun (l, j) => (complementBlock i j l).length).sum ≤ + (c.zipIdx.map fun _ => 4).sum := + List.sum_le_sum fun a _ => length_complementBlock_le i a.2 a.1 + simpa [Nat.mul_comm] using h + +/-- Width at most `k` gives a factor `1 + 4*k` bound on the number of clauses. -/ +theorem length_formula_le {k : ℕ} {φ : CNF V} (hw : ∀ c ∈ φ, c.length ≤ k) : + (formula φ).length ≤ (1 + 4 * k) * φ.length := by + simp only [formula, List.length_flatMap] + have h : (φ.zipIdx.map fun (c, i) => (clauseClauses i c).length).sum ≤ + (φ.zipIdx.map fun _ => 1 + 4 * k).sum := by + apply List.sum_le_sum + intro a ha + exact (length_clauseClauses_le a.2 a.1).trans + (Nat.add_le_add_left (Nat.mul_le_mul_left _ (hw _ (List.fst_mem_of_mem_zipIdx ha))) _) + simpa [Nat.mul_comm] using h + +/-- All literals in a complement block are positive. -/ +theorem positive_complementBlock (i j : ℕ) (l : Literal V) : + ∀ c ∈ complementBlock i j l, ∀ a ∈ c, a.2 = true := by + unfold complementBlock + split + · simp + · exact positive_complementClauses _ _ _ _ _ _ + +/-- Every literal emitted by the compiler is positive. -/ +theorem positive_formula (φ : CNF V) : + ∀ c ∈ formula φ, ∀ l ∈ c, l.2 = true := by + intro c hc l hl + obtain ⟨⟨a, i⟩, _, hc⟩ := List.mem_flatMap.mp hc + rcases List.mem_append.mp hc with hc | hc + · obtain rfl := List.mem_singleton.mp hc + obtain ⟨⟨b, j⟩, _, rfl⟩ := List.mem_map.mp hl + rfl + · obtain ⟨⟨b, j⟩, _, hc⟩ := List.mem_flatMap.mp hc + exact positive_complementBlock i j b c hc l hl + +/-- The inherited clause retains its width and every complement clause has width three. -/ +theorem width_clauseClauses {k : ℕ} (hk : 3 ≤ k) (i : ℕ) (c : Clause V) + (hc : c.length ≤ k) : ∀ a ∈ clauseClauses i c, a.length ≤ k := by + intro a ha + rcases List.mem_append.mp ha with ha | ha + · obtain rfl := List.mem_singleton.mp ha + simpa [Nat.mul_comm] using hc + · obtain ⟨⟨l, j⟩, _, ha⟩ := List.mem_flatMap.mp ha + change a ∈ complementBlock i j l at ha + unfold complementBlock at ha + split at ha + · simp at ha + · exact (width_complementClauses _ _ _ _ _ _ a ha).le.trans hk + +/-- Exact-three input clauses yield exact-three positive clauses. -/ +theorem exactWidth_formula {φ : CNF V} (hw : ∀ c ∈ φ, c.length = 3) : + ∀ c ∈ formula φ, c.length = 3 := by + intro c hc + obtain ⟨⟨a, i⟩, ha, hc⟩ := List.mem_flatMap.mp hc + rcases List.mem_append.mp hc with hc | hc + · obtain rfl := List.mem_singleton.mp hc + simpa [Nat.mul_comm] using hw a (List.fst_mem_of_mem_zipIdx ha) + · obtain ⟨⟨l, j⟩, _, hc⟩ := List.mem_flatMap.mp hc + change c ∈ complementBlock i j l at hc + unfold complementBlock at hc + split at hc + · simp at hc + · exact width_complementClauses _ _ _ _ _ _ _ hc + +/-- Variable occurrences contributed by one transformed source literal, including its private +complement gadget when needed. -/ +def occurrenceVariables (i j : ℕ) (l : Literal V) : List (Variable V) := + [(positiveLiteral i j l).1] ++ (complementBlock i j l).vars + +/-- Distinct source variables remain distinct in the inherited positive clause. -/ +theorem nodup_inheritedClause (i : ℕ) (c : Clause V) (hc : (c.map Prod.fst).Nodup) : + ((c.zipIdx.map fun (l, j) => positiveLiteral i j l).map Prod.fst).Nodup := by + have hvars : (c.zipIdx.map fun a => a.1.1).Nodup := by + have he := congrArg (List.map Prod.fst) (List.zipIdx_map_fst 0 c) + simp only [List.map_map, Function.comp_def] at he + rw [he] + exact hc + have hidx := List.nodup_zipIdx_map_snd c + change List.Pairwise _ _ at hvars hidx ⊢ + rw [List.map_map, List.pairwise_map] + have hboth := (List.pairwise_map.mp hvars).and (List.pairwise_map.mp hidx) + apply hboth.imp + intro a b hab + rcases a with ⟨⟨v, p⟩, j⟩ + rcases b with ⟨⟨w, q⟩, k⟩ + cases p <;> cases q <;> simp_all [positiveLiteral, auxiliary] + +/-- Every complement clause has three distinct variables, regardless of its source literal. -/ +theorem nodup_complementBlock (i j : ℕ) (l : Literal V) : + ∀ c ∈ complementBlock i j l, (c.map Prod.fst).Nodup := by + unfold complementBlock + split + · simp + · simp [complementClauses, forceFalseClauses, auxiliary] + +/-- Distinct variables in every source clause give distinct variables in every output clause. -/ +theorem nodup_formula {φ : CNF V} (hn : ∀ c ∈ φ, (c.map Prod.fst).Nodup) : + ∀ c ∈ formula φ, (c.map Prod.fst).Nodup := by + intro c hc + obtain ⟨⟨a, i⟩, ha, hc⟩ := List.mem_flatMap.mp hc + rcases List.mem_append.mp hc with hc | hc + · obtain rfl := List.mem_singleton.mp hc + exact nodup_inheritedClause i a (hn a (List.fst_mem_of_mem_zipIdx ha)) + · obtain ⟨⟨l, j⟩, _, hc⟩ := List.mem_flatMap.mp hc + exact nodup_complementBlock i j l c hc + +/-- Width at most three is preserved. -/ +theorem width_formula {φ : CNF V} (hw : ∀ c ∈ φ, c.length ≤ 3) : + ∀ c ∈ formula φ, c.length ≤ 3 := by + intro c hc + obtain ⟨⟨a, i⟩, ha, hc⟩ := List.mem_flatMap.mp hc + exact width_clauseClauses (by decide) i a (hw a (List.fst_mem_of_mem_zipIdx ha)) c hc + +/-- Width-three inputs have at most thirty-nine output literal occurrences per source clause. -/ +theorem length_vars_formula_le {φ : CNF V} (hw : ∀ c ∈ φ, c.length ≤ 3) : + (formula φ).vars.length ≤ 39 * φ.length := by + have h := CNF.length_vars_le_mul_length (width_formula hw) + exact h.trans (by simpa [← Nat.mul_assoc] using Nat.mul_le_mul_left 3 (length_formula_le hw)) + +private theorem count_map_append_flatMap {α β : Type*} [BEq β] + (as : List α) (f : α → β) (g : α → List β) (v : β) : + (as.map f ++ as.flatMap g).count v = + (as.map fun a => ([f a] ++ g a).count v).sum := by + induction as with + | nil => simp + | cons a as ih => + simp only [List.map_cons, List.flatMap_cons, List.cons_append, List.count_cons, + List.count_append, List.sum_cons, List.count_nil] at * + split_ifs <;> omega + +/-- Clause-block variable counts are the sum of the individual occurrence contributions. -/ +theorem count_vars_clauseClauses [DecidableEq V] (i : ℕ) (c : Clause V) + (v : Variable V) : + (clauseClauses i c).vars.count v = + (c.zipIdx.map fun a => (occurrenceVariables i a.2 a.1).count v).sum := by + have hv : (clauseClauses i c).vars = + c.zipIdx.map (fun a => (positiveLiteral i a.2 a.1).1) ++ + c.zipIdx.flatMap (fun a => (complementBlock i a.2 a.1).vars) := by + simp [clauseClauses, CNF.vars, List.flatMap_def, List.map_flatten, + List.flatten_flatten, List.map_map, Function.comp_def] + rw [hv] + exact count_map_append_flatMap c.zipIdx (fun a => (positiveLiteral i a.2 a.1).1) + (fun a => (complementBlock i a.2 a.1).vars) v + +/-- An original variable contributes exactly one occurrence at its original literal position. -/ +theorem count_inl_occurrenceVariables [DecidableEq V] (i j : ℕ) (l : Literal V) + (v : V) : (occurrenceVariables i j l).count (Sum.inl v) = [l.1].count v := by + cases hb : l.2 <;> + simp [occurrenceVariables, positiveLiteral, complementBlock, complementClauses, + forceFalseClauses, auxiliary, CNF.vars, List.count_cons, hb] + +/-- A private occurrence contribution uses only its own clause and literal indices and has +degree at most three at each auxiliary. -/ +theorem count_aux_occurrenceVariables [DecidableEq V] (i j a b k : ℕ) (l : Literal V) : + (occurrenceVariables i j l).count (auxiliary a b k) ≤ + if a = i ∧ b = j then 3 else 0 := by + by_cases hi : a = i + · subst a + by_cases hj : b = j + · subst b + by_cases hk : k < 5 + · interval_cases k <;> cases hb : l.2 <;> + simp [occurrenceVariables, positiveLiteral, complementBlock, complementClauses, + forceFalseClauses, auxiliary, CNF.vars, hb] + · have h0 : 0 ≠ k := by omega + have h1 : 1 ≠ k := by omega + have h2 : 2 ≠ k := by omega + have h3 : 3 ≠ k := by omega + have h4 : 4 ≠ k := by omega + cases hb : l.2 <;> + simp [occurrenceVariables, positiveLiteral, complementBlock, + complementClauses, forceFalseClauses, auxiliary, CNF.vars, hb, + h0, h1, h2, h3, h4] + · cases hb : l.2 <;> + simp [occurrenceVariables, positiveLiteral, complementBlock, complementClauses, + forceFalseClauses, auxiliary, CNF.vars, hb, hj, Ne.symm hj] + · cases hb : l.2 <;> + simp [occurrenceVariables, positiveLiteral, complementBlock, complementClauses, + forceFalseClauses, auxiliary, CNF.vars, hb, hi, Ne.symm hi] + +/-- Original variable occurrence counts are unchanged in each compiled clause block. -/ +theorem count_inl_clause [DecidableEq V] (i : ℕ) (c : Clause V) (v : V) : + (clauseClauses i c).vars.count (Sum.inl v) = (CNF.vars [c]).count v := by + rw [count_vars_clauseClauses] + simp only [count_inl_occurrenceVariables] + have h : (c.zipIdx.flatMap fun a => [a.1.1]).count v = + (c.zipIdx.map fun a => [a.1.1].count v).sum := by + rw [List.count_flatMap] + rfl + rw [← h] + have he : c.zipIdx.flatMap (fun a => [a.1.1]) = c.zipIdx.map (fun a => a.1.1) := by + induction c.zipIdx with + | nil => rfl + | cons a as ih => simp [ih] + rw [he] + simp only [CNF.vars, List.flatten_cons, List.flatten_nil, List.append_nil] + simpa only [List.map_map, Function.comp_def] using + congrArg (fun ls : List (Literal V) => (ls.map Prod.fst).count v) + (List.zipIdx_map_fst 0 c) + +/-- An auxiliary contributes at most three occurrences in a clause block, and no occurrences +outside its own source clause index. -/ +theorem count_aux_clause [DecidableEq V] (i a j k : ℕ) (c : Clause V) : + (clauseClauses i c).vars.count (auxiliary a j k) ≤ if a = i then 3 else 0 := by + rw [count_vars_clauseClauses] + calc + _ ≤ (c.zipIdx.map fun l => if a = i ∧ j = l.2 then 3 else 0).sum := + List.sum_le_sum fun l _ => count_aux_occurrenceVariables i l.2 a j k l.1 + _ ≤ if a = i then 3 else 0 := by + by_cases hi : a = i + · simp only [hi, true_and, ite_true] + have he : (c.zipIdx.map fun l => if j = l.2 then 3 else 0).sum = + (c.zipIdx.map Prod.snd).count j * 3 := by + calc + _ = ((c.zipIdx.map Prod.snd).map fun b => if j = b then 3 else 0).sum := by + rw [List.map_map] + rfl + _ = _ := by + rw [List.sum_map_eq_nsmul_single j _ (fun _ h _ => by simp [Ne.symm h])] + simp + rw [he] + have hcount := List.nodup_iff_count_le_one.mp (List.nodup_zipIdx_map_snd c) j + omega + · simp [hi] + +/-- Taking variable occurrences commutes with concatenating clause replacements. -/ +theorem vars_formula (φ : CNF V) : + (formula φ).vars = φ.zipIdx.flatMap fun c => (clauseClauses c.2 c.1).vars := by + simp [formula, CNF.vars, List.flatMap_def, List.map_flatten, List.flatten_flatten, + List.map_map, Function.comp_def] + +/-- Every original variable keeps exactly its original number of literal occurrences. -/ +theorem count_inl_formula [DecidableEq V] (φ : CNF V) (v : V) : + (formula φ).vars.count (Sum.inl v) = φ.vars.count v := by + rw [vars_formula, List.count_flatMap] + have hlocal (c : Clause V × ℕ) : + (clauseClauses c.2 c.1).vars.count (Sum.inl v) = (CNF.vars [c.1]).count v := + count_inl_clause c.2 c.1 v + simp only [Function.comp_def] + rw [List.map_congr_left (fun c _ => hlocal c)] + simp only [CNF.vars, List.flatten_cons, List.flatten_nil, List.append_nil, + List.map_flatten, List.count_flatten, List.map_map, Function.comp_def] + simpa only [List.map_map, Function.comp_def] using + congrArg (fun cs : List (Clause V) => (cs.map fun c => (c.map Prod.fst).count v).sum) + (List.zipIdx_map_fst 0 φ) + +/-- Every private auxiliary has at most three occurrences in the whole output. -/ +theorem count_aux_formula [DecidableEq V] (φ : CNF V) (i j k : ℕ) : + (formula φ).vars.count (auxiliary i j k) ≤ 3 := by + rw [vars_formula, List.count_flatMap] + calc + _ ≤ (φ.zipIdx.map fun c => if i = c.2 then 3 else 0).sum := + List.sum_le_sum fun c _ => count_aux_clause _ _ _ _ _ + _ = (φ.zipIdx.map Prod.snd).count i * 3 := by + calc + _ = ((φ.zipIdx.map Prod.snd).map fun b => if i = b then 3 else 0).sum := by + rw [List.map_map] + rfl + _ = _ := by + rw [List.sum_map_eq_nsmul_single i _ (fun _ h _ => by simp [Ne.symm h])] + simp + _ ≤ 3 := by + have hcount := List.nodup_iff_count_le_one.mp (List.nodup_zipIdx_map_snd φ) i + omega + +/-- The positive compiler preserves the three-occurrence restriction. -/ +theorem count_vars_formula_le [DecidableEq V] (φ : CNF V) + (ho : ∀ v, φ.vars.count v ≤ 3) (v : Variable V) : + (formula φ).vars.count v ≤ 3 := by + cases v with + | inl v => rw [count_inl_formula]; exact ho v + | inr v => exact count_aux_formula φ v.1.1 v.1.2 v.2 + +end Cslib.Logic.PL.CNF.PositiveExactOne diff --git a/ComplexityTests/PositiveOneInThreeSAT.lean b/ComplexityTests/PositiveOneInThreeSAT.lean new file mode 100644 index 000000000..e577dd1a2 --- /dev/null +++ b/ComplexityTests/PositiveOneInThreeSAT.lean @@ -0,0 +1,119 @@ +/- +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.OneInThreeSAT.Reduction + +/-! +# Positive exact-one SAT regression examples + +Tests distinguish exact-one from ordinary disjunction, check repeated occurrences and +nonvacuous positive degree-three examples, and apply the full parsimonious compiler to +short clauses, an empty clause, and an empty formula. +-/ + +public section + +namespace Cslib.Tests.PositiveOneInThreeSAT + +open Cslib.Logic.PL Cslib.Complexity + +private def oneTriple : CNF ℕ := [[(0, true), (1, true), (2, true)]] + +example : oneTriple.oneModelCount = 3 := by decide +example : oneTriple.modelCount = 7 := by decide +example : oneTriple ∈ PositiveOneInExactThreeSATThree := by + refine ⟨⟨by decide, rfl, fun v => v == 0, by decide⟩, ?_⟩ + intro v + by_cases h₀ : v = 0 + · subst v; decide + by_cases h₁ : v = 1 + · subst v; decide + by_cases h₂ : v = 2 + · subst v; decide + simp [oneTriple, CNF.vars, Ne.symm h₀, Ne.symm h₁, Ne.symm h₂] + +/-- The four triples on four variables have no exact-one assignment, although every variable +has degree three and every clause is positive and distinct. -/ +private def fourTriples : CNF ℕ := + [[(0, true), (1, true), (2, true)], [(0, true), (1, true), (3, true)], + [(0, true), (2, true), (3, true)], [(1, true), (2, true), (3, true)]] + +example : fourTriples.oneModelCount = 0 := by decide +example : ¬fourTriples.OneSatisfiable := by + rw [← CNF.oneModelCount_pos_iff] + decide + +/-- Repeated variables count as repeated literal positions. -/ +example : (CNF.oneModelCount [[(0, true), (0, true), (0, true)]] : ℕ) = 0 := by decide + +/-- The empty positive instance has exactly one assignment on its empty support. -/ +theorem empty_formula_count : CNF.oneModelCount ([] : CNF ℕ) = 1 := by decide +example : CNF.oneModelCount ([[]] : CNF ℕ) = 0 := by decide + +example : CNF.oneModelCount [[(0, true), (0, false)]] = 2 := by decide + +example : OneSAT.positiveCheck [[(0, false)]] = false := rfl + +example : OneSAT.exactClauseCheck [(0, true), (0, true), (1, true)] = false := rfl + +private def binarySource : CNF ℕ := [[(0, true), (1, true)]] + +/-- The compiler preserves three models of a binary disjunction without enumerating its +many fresh variables. -/ +example : (toPositiveOneInExactThreeSATThree binarySource).oneModelCount = 3 := by + rw [oneModelCount_toPositiveOneInExactThreeSATThree _ (by decide)] + decide + +example : (toPositiveOneInExactThreeSATThree ([] : CNF ℕ)).oneModelCount = 1 := by + rw [oneModelCount_toPositiveOneInExactThreeSATThree _ (by simp)] + decide + +example : (toPositiveOneInExactThreeSATThree ([[]] : CNF ℕ)).oneModelCount = 0 := by + rw [oneModelCount_toPositiveOneInExactThreeSATThree _ (by decide)] + decide + +example : (toPositiveOneInExactThreeSATThree binarySource).length ≤ 507 := by + have h := (size_toPositiveOneInExactThreeSATThree binarySource (by decide)).1 + simpa [binarySource, CNF.vars] using h + +example : (toPositiveOneInExactThreeSATThree binarySource).vars.length ≤ 1521 := by + have h := (size_toPositiveOneInExactThreeSATThree binarySource (by decide)).2 + simpa [binarySource, CNF.vars] using h + +/-- Size bounds do not depend on the numeric magnitude of binary source labels. -/ +example : (toPositiveOneInExactThreeSATThree [[(2 ^ 100, true)]]).length ≤ 338 := by + have h := (size_toPositiveOneInExactThreeSATThree [[(2 ^ 100, true)]] (by decide)).1 + simpa [CNF.vars] using h + +example : (toPositiveOneInExactThreeSATThree [[(0, true), (0, false)]]).oneModelCount = 2 := by + rw [oneModelCount_toPositiveOneInExactThreeSATThree _ (by decide)] + decide + +/-- Malformed width-four syntax is rejected with count zero by the total reduction. -/ +example : SharpPositiveOneInExactThreeSATThree + (toPositiveOneInExactThreeSATThree + [[(0, true), (0, false), (0, true), (0, false)]]) = 0 := by + rw [sharpPositiveOneInExactThreeSATThree_toPositiveOneInExactThreeSATThree] + decide + +example : CNF.ExactOneGadget.DisjunctionHolds false true true true false false true true := by + decide + +/-- The formerly ambiguous input has exactly the canonical values. -/ +example (u v w t x : Bool) : + CNF.ExactOneGadget.DisjunctionHolds false true true u v w t x ↔ + u = true ∧ v = false ∧ w = false ∧ t = true ∧ x = true := by + simpa [CNF.ExactOneGadget.firstAux, CNF.ExactOneGadget.secondAux, + CNF.ExactOneGadget.thirdAux, CNF.ExactOneGadget.fourthAux, + CNF.ExactOneGadget.fifthAux] using + CNF.ExactOneGadget.disjunctionHolds_iff false true true u v w t x + +end Cslib.Tests.PositiveOneInThreeSAT + +end From 5944f11bbc6ae3e83ef07784f25d8395db57483f Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 21:29:34 -0400 Subject: [PATCH 2/5] feat(geometry): certify planar positive exact-one clause replacements Refs #1258, depends on #1257. The fifteen actual positive clause profiles and ordered three-terminal collars have kernel-checked full real-support certificates and explicit costs. Reusable scaling, trimming, joining, reflection and direction lemmas live in foundational geometry modules. Co-developed with Codex workers t3-96187517-positive-gadgets and t3-96187517-positive-local-geometry. This milestone provides local certificates, not global planar hardness. Exact-revision validation and independent coordinator review will be recorded in the PR. Selected abstracts and rendering are unchanged. --- Complexity.lean | 18 + .../Problems/SAT/ExactOne/Geometry.lean | 194 +++ .../Problems/SAT/ExactOne/GeometryCollar.lean | 150 ++ .../SAT/ExactOne/GeometryCollarData.lean | 213 +++ .../SAT/ExactOne/GeometryCollarGlue.lean | 361 +++++ .../Problems/SAT/ExactOne/GeometryData.lean | 148 ++ .../Problems/SAT/ExactOne/GeometryPrefix.lean | 169 ++ .../SAT/ExactOne/GeometryProfiles.lean | 80 + .../SAT/ExactOne/GeometryProfilesCollar.lean | 362 +++++ .../SAT/ExactOne/GeometryProfilesData.lean | 1373 +++++++++++++++++ .../SAT/ExactOne/GeometryProfilesPrefix.lean | 198 +++ .../ExactOne/GeometryProfilesPrefixData.lean | 36 + .../Geometry/Orthogonal/Directions.lean | 85 + .../Geometry/Orthogonal/Isolation.lean | 218 +++ .../Foundations/Geometry/Orthogonal/Join.lean | 116 ++ .../Geometry/Orthogonal/Mirror.lean | 145 ++ .../Geometry/Orthogonal/Reverse.lean | 117 ++ .../Geometry/Orthogonal/Scale.lean | 162 ++ .../Geometry/Orthogonal/Subdivision.lean | 21 +- .../Foundations/Geometry/Orthogonal/Trim.lean | 290 ++++ 20 files changed, 4450 insertions(+), 6 deletions(-) create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/Geometry.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollar.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollarData.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollarGlue.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryData.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryPrefix.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfiles.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesCollar.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesData.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesPrefix.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesPrefixData.lean create mode 100644 Complexity/Foundations/Geometry/Orthogonal/Directions.lean create mode 100644 Complexity/Foundations/Geometry/Orthogonal/Isolation.lean create mode 100644 Complexity/Foundations/Geometry/Orthogonal/Join.lean create mode 100644 Complexity/Foundations/Geometry/Orthogonal/Mirror.lean create mode 100644 Complexity/Foundations/Geometry/Orthogonal/Reverse.lean create mode 100644 Complexity/Foundations/Geometry/Orthogonal/Scale.lean create mode 100644 Complexity/Foundations/Geometry/Orthogonal/Trim.lean diff --git a/Complexity.lean b/Complexity.lean index b0a122b46..f8f2bcd94 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -812,6 +812,17 @@ public import Complexity.Computability.Complexity.Problems.OneInThreeSAT.Reducti public import Complexity.Computability.Complexity.Problems.Partition public import Complexity.Computability.Complexity.Problems.PlanarThreeSATThree public import Complexity.Computability.Complexity.Problems.SAT +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.Geometry +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryCollar +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryCollarData +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryCollarGlue +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryData +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryPrefix +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfiles +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesCollar +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesData +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesPrefix +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesPrefixData public import Complexity.Computability.Complexity.Problems.SAT.Incidence public import Complexity.Computability.Complexity.Problems.SAT.Incidence.Compact public import Complexity.Computability.Complexity.Problems.SAT.Incidence.Degree @@ -1997,16 +2008,23 @@ public import Complexity.Foundations.FieldTheory.TensorBasis public import Complexity.Foundations.Geometry.Euclidean.Angle.Cap public import Complexity.Foundations.Geometry.Euclidean.Angle.Unoriented.Basic public import Complexity.Foundations.Geometry.Orthogonal +public import Complexity.Foundations.Geometry.Orthogonal.Directions public import Complexity.Foundations.Geometry.Orthogonal.Endpoint +public import Complexity.Foundations.Geometry.Orthogonal.Isolation +public import Complexity.Foundations.Geometry.Orthogonal.Join public import Complexity.Foundations.Geometry.Orthogonal.Layout public import Complexity.Foundations.Geometry.Orthogonal.Macrocell +public import Complexity.Foundations.Geometry.Orthogonal.Mirror 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.Reverse +public import Complexity.Foundations.Geometry.Orthogonal.Scale public import Complexity.Foundations.Geometry.Orthogonal.Separation public import Complexity.Foundations.Geometry.Orthogonal.Simple public import Complexity.Foundations.Geometry.Orthogonal.Subdivision +public import Complexity.Foundations.Geometry.Orthogonal.Trim public import Complexity.Foundations.GroupTheory.Perm.Fin public import Complexity.Foundations.GroupTheory.Perm.FiveCycle public import Complexity.Foundations.GroupTheory.Perm.Last diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/Geometry.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/Geometry.lean new file mode 100644 index 000000000..32375b92d --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/Geometry.lean @@ -0,0 +1,194 @@ +/- +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.ExactOne.GeometryData +public import Complexity.Foundations.Geometry.Orthogonal.Separation +public import Complexity.Foundations.Geometry.Orthogonal.Simple +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Nodup +import Mathlib.Data.Fintype.Pi + +/-! +# Continuous geometry certificates for exact-one gadgets + +Finite arithmetic checks certify the entire real supports of the fixed gadget routes, +including their isolated exterior accesses. These are local replacement certificates; +composition with an input drawing is a separate construction. + +## Main definitions + +* `LocalGeometry.Check`: decidable finite endpoint, simplicity, contact, and access checks. + +## Main results + +* `disjunctionGeometry_check`, `falseGeometry_check`, `complementGeometry_check`. +* `LocalGeometry.incidence_isSimple`, `LocalGeometry.routes_contact`, + `LocalGeometry.access_contact`: continuous consequences of the finite checks. + +## References + +The incidence graphs are from [HuntMaratheRadhakrishnanStearns1998, Theorem 3.8, +Figures 3.4–3.5 and Lemma 3.6]. The orthogonal certificates are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Orthogonal + +namespace LocalGeometry + +/-- Common listed endpoints of two segments are the permitted self-contacts. -/ +def segmentEndpoints (a b : Point × Point) : List Point := + ([a.1, a.2].filter fun p => p = b.1 ∨ p = b.2) + +/-- A decidable finite simplicity certificate for an individual polygonal route. -/ +def SimpleCheck (r : Route) : Prop := + r.Nodup ∧ ∀ a ∈ r.consecutivePairs, ∀ b ∈ r.consecutivePairs, a ≠ b → + segmentContactCheck (segmentEndpoints a b) a.1 a.2 b.1 b.2 = true + +instance (r : Route) : Decidable (SimpleCheck r) := + inferInstanceAs (Decidable (_ ∧ _)) + +/-- The finite certificate refers to graph endpoints, complete routes, and all exterior +accesses. The core lies strictly inside the square cell of side `g.size`. -/ +def Check (g : LocalGeometry) : Prop := + (g.variablePositions ++ g.clausePositions).Nodup ∧ + (g.incidenceRoutes.map fun e => (e.1, e.2.1)).Nodup ∧ + g.accessRoutes.length = g.terminalVariables.length ∧ + g.accessRoutes.length = g.boundaryPorts.length ∧ + (∀ e ∈ g.incidenceRoutes, + e.1 < g.clausePositions.length ∧ e.2.1 < g.variablePositions.length ∧ + e.2.2.head? = some (g.clausePosition e.1) ∧ + e.2.2.getLast? = some (g.variablePosition e.2.1) ∧ + IsOrthogonal e.2.2 ∧ SimpleCheck e.2.2 ∧ + ∀ p ∈ e.2.2, 20 ≤ p.1 ∧ p.1 ≤ (g.size : ℤ) - 20 ∧ + 20 ≤ p.2 ∧ p.2 ≤ (g.size : ℤ) - 20) ∧ + (∀ e ∈ g.incidenceRoutes, ∀ f ∈ g.incidenceRoutes, e ≠ f → + routeContactCheck (g.commonEndpoints e f) e.2.2 f.2.2 = true) ∧ + (∀ e ∈ g.incidenceRoutes, ∀ v : Fin g.variablePositions.length, v.val ≠ e.2.1 → + routeContactCheck [] e.2.2 [g.variablePosition v] = true) ∧ + (∀ e ∈ g.incidenceRoutes, ∀ i : Fin g.clausePositions.length, i.val ≠ e.1 → + routeContactCheck [] e.2.2 [g.clausePosition i] = true) ∧ + (∀ i : Fin g.accessRoutes.length, + let r := g.accessRoutes.getD i [] + let v := g.terminalVariables.getD i 0 + v < g.variablePositions.length ∧ + r.head? = some (g.boundaryPorts.getD i (0, 0)) ∧ + r.getLast? = some (g.variablePosition v) ∧ + IsOrthogonal r ∧ SimpleCheck r ∧ + (∀ p ∈ r, 0 ≤ p.1 ∧ p.1 ≤ g.size ∧ 0 ≤ p.2 ∧ p.2 ≤ g.size) ∧ + (∀ p ∈ r.tail, 0 < p.1 ∧ p.1 < g.size ∧ 0 < p.2 ∧ p.2 < g.size) ∧ + (∀ e ∈ g.incidenceRoutes, + routeContactCheck (if e.2.1 = v then [g.variablePosition v] else []) + r e.2.2 = true) ∧ + (∀ j : Fin g.accessRoutes.length, i ≠ j → + routeContactCheck [] r (g.accessRoutes.getD j []) = true) ∧ + (∀ w : Fin g.variablePositions.length, w.val ≠ v → + routeContactCheck [] r [g.variablePosition w] = true) ∧ + ∀ p ∈ g.clausePositions, routeContactCheck [] r [p] = true) + +instance (g : LocalGeometry) : Decidable (Check g) := by + unfold Check + repeat' refine @instDecidableAnd _ _ ?_ ?_ + all_goals infer_instance + +/-- The finite simplicity certificate controls full real segment intersections. -/ +theorem isSimple_of_check {r : Route} (h : SimpleCheck r) : IsSimple r := by + refine ⟨h.1, ?_⟩ + intro a ha b hb hne p hp hq + obtain ⟨q, hq, hpq⟩ := segmentContactCheck_sound (h.2 a ha b hb hne) hp hq + have hq' := List.mem_filter.mp hq + have hqa : q = a.1 ∨ q = a.2 := by simpa using hq'.1 + have hqb : q = b.1 ∨ q = b.2 := by simpa using hq'.2 + exact ⟨hqa.elim (fun h => Or.inl (h ▸ hpq)) (fun h => Or.inr (h ▸ hpq)), + hqb.elim (fun h => Or.inl (h ▸ hpq)) (fun h => Or.inr (h ▸ hpq))⟩ + +/-- Every checked incidence has its prescribed clause and variable endpoints. -/ +theorem incidence_endpoints {g : LocalGeometry} (h : Check g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) : + e.1 < g.clausePositions.length ∧ e.2.1 < g.variablePositions.length ∧ + e.2.2.head? = some (g.clausePosition e.1) ∧ + e.2.2.getLast? = some (g.variablePosition e.2.1) := by + have he' := h.2.2.2.2.1 e he + exact ⟨he'.1, he'.2.1, he'.2.2.1, he'.2.2.2.1⟩ + +/-- Every checked incidence is properly orthogonal. -/ +theorem incidence_orthogonal {g : LocalGeometry} (h : Check g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) : IsOrthogonal e.2.2 := + (h.2.2.2.2.1 e he).2.2.2.2.1 + +/-- Every checked incidence is simple over its full continuous support. -/ +theorem incidence_isSimple {g : LocalGeometry} (h : Check g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) : IsSimple e.2.2 := + isSimple_of_check (h.2.2.2.2.1 e he).2.2.2.2.2.1 + +/-- Checked incidence routes can meet only at a shared clause or variable vertex. -/ +theorem routes_contact {g : LocalGeometry} (h : Check g) + {e f : LocalIncidence} (he : e ∈ g.incidenceRoutes) (hf : f ∈ g.incidenceRoutes) + (hne : e ≠ f) {p : ℝ × ℝ} (hp : p ∈ support e.2.2) (hq : p ∈ support f.2.2) : + ∃ q ∈ g.commonEndpoints e f, p = realPoint q := + routeContactCheck_sound (h.2.2.2.2.2.1 e he f hf hne) hp hq + +/-- Checked incidences avoid every nonincident variable vertex. -/ +theorem variable_not_mem_support {g : LocalGeometry} (h : Check g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) + {v : Fin g.variablePositions.length} (hv : v.val ≠ e.2.1) : + realPoint (g.variablePosition v) ∉ support e.2.2 := + not_mem_support_of_routeContactCheck (h.2.2.2.2.2.2.1 e he v hv) + +/-- Checked incidences avoid every nonincident clause vertex. -/ +theorem clause_not_mem_support {g : LocalGeometry} (h : Check g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) + {i : Fin g.clausePositions.length} (hi : i.val ≠ e.1) : + realPoint (g.clausePosition i) ∉ support e.2.2 := + not_mem_support_of_routeContactCheck (h.2.2.2.2.2.2.2.1 e he i hi) + +/-- Exterior access routes are simple over their full continuous support. -/ +theorem access_isSimple {g : LocalGeometry} (h : Check g) (i : Fin g.accessRoutes.length) : + IsSimple (g.accessRoutes.getD i []) := + isSimple_of_check (h.2.2.2.2.2.2.2.2 i).2.2.2.2.1 + +/-- Distinct checked accesses have disjoint full real supports. -/ +theorem access_disjoint {g : LocalGeometry} (h : Check g) + {i j : Fin g.accessRoutes.length} (hne : i ≠ j) : + Disjoint (support (g.accessRoutes.getD i [])) (support (g.accessRoutes.getD j [])) := + disjoint_support_of_routeContactCheck + ((h.2.2.2.2.2.2.2.2 i).2.2.2.2.2.2.2.2.1 j hne) + +/-- A checked access meets the core only at its corresponding terminal. -/ +theorem access_contact {g : LocalGeometry} (h : Check g) + {i : Fin g.accessRoutes.length} {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) + {p : ℝ × ℝ} (hp : p ∈ support (g.accessRoutes.getD i [])) + (hq : p ∈ support e.2.2) : + e.2.1 = g.terminalVariables.getD i 0 ∧ + p = realPoint (g.variablePosition (g.terminalVariables.getD i 0)) := by + obtain ⟨q, hq, hpq⟩ := routeContactCheck_sound + ((h.2.2.2.2.2.2.2.2 i).2.2.2.2.2.2.2.1 e he) hp hq + split_ifs at hq with heq + · have hq' := List.mem_singleton.mp hq + exact ⟨heq, hq' ▸ hpq⟩ + · exact False.elim (List.not_mem_nil hq) + +end LocalGeometry + +/-- Complete finite certificate for the parsimonious disjunction replacement. -/ +theorem disjunctionGeometry_check : LocalGeometry.Check disjunctionGeometry := by + decide +kernel + +/-- Complete finite certificate for the positive forced-false gadget. -/ +theorem falseGeometry_check : LocalGeometry.Check falseGeometry := by + decide +kernel + +/-- Complete finite certificate for the positive complement gadget. -/ +theorem complementGeometry_check : LocalGeometry.Check complementGeometry := by + decide +kernel + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollar.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollar.lean new file mode 100644 index 000000000..17ca08966 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollar.lean @@ -0,0 +1,150 @@ +/- +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.ExactOne.GeometryCollarData +public import Complexity.Foundations.Geometry.Orthogonal.Trim +import Mathlib.Data.Fintype.Pi +import Mathlib.Tactic.Linarith + +/-! +# Certified annular routing for ordered clause ports + +All 24 ordered injections into the four side midpoints have three mutually disjoint +simple orthogonal connector routes. Every connector meets the closed inner template square +only at its prescribed endpoint. The certificates control entire real supports. + +## Main definitions + +* `routeBoxContactCheck`: a finite route/closed-square contact certificate. +* `CollarCheck`: endpoint, simplicity, bounds, disjointness, and inner-square checks. + +## Main results + +* `collar_check`: complete finite certificates for every valid ordered assignment. +* `collar_core_contact`, `collar_disjoint`: full continuous isolation consequences. + +## References + +The finite annular routing certificates and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Orthogonal + +/-- A bounding-box contact test between every route segment and a closed square. -/ +def routeBoxContactCheck (allowed : List Point) (r : Route) (c : Point) (radius : ℕ) : Bool := + (segmentPieces r).all fun e => segmentContactCheck allowed e.1 e.2 + (c.1 - radius, c.2 - radius) (c.1 + radius, c.2 + radius) + +private theorem segment_box_contact_sound {allowed : List Point} {a b c : Point} + {radius : ℕ} + (h : segmentContactCheck allowed a b + (c.1 - radius, c.2 - radius) (c.1 + radius, c.2 + radius) = true) + {p : ℝ × ℝ} (hp : p ∈ segment a b) (hc : inPointBox c radius p) : + ∃ q ∈ allowed, p = realPoint q := by + have hab := mem_segment_bounds hp + change (min (a.1 : ℝ) (b.1 : ℝ) ≤ p.1 ∧ p.1 ≤ max (a.1 : ℝ) (b.1 : ℝ)) ∧ + (min (a.2 : ℝ) (b.2 : ℝ) ≤ p.2 ∧ p.2 ≤ max (a.2 : ℝ) (b.2 : ℝ)) at hab + let lx := max (min a.1 b.1) (c.1 - radius) + let ux := min (max a.1 b.1) (c.1 + radius) + let ly := max (min a.2 b.2) (c.2 - radius) + let uy := min (max a.2 b.2) (c.2 + radius) + have hx : (lx : ℝ) ≤ p.1 ∧ p.1 ≤ (ux : ℝ) := by + dsimp only [lx, ux] + simp only [Int.cast_max, Int.cast_min, Int.cast_sub, Int.cast_add, Int.cast_natCast] + exact ⟨max_le hab.1.1 hc.1, le_min hab.1.2 hc.2.1⟩ + have hy : (ly : ℝ) ≤ p.2 ∧ p.2 ≤ (uy : ℝ) := by + dsimp only [ly, uy] + simp only [Int.cast_max, Int.cast_min, Int.cast_sub, Int.cast_add, Int.cast_natCast] + exact ⟨max_le hab.2.1 hc.2.2.1, le_min hab.2.2 hc.2.2.2⟩ + have hlo₁ : c.1 - (radius : ℤ) ≤ c.1 + radius := by omega + have hlo₂ : c.2 - (radius : ℤ) ≤ c.2 + radius := by omega + simp only [segmentContactCheck, min_eq_left hlo₁, max_eq_right hlo₁, + min_eq_left hlo₂, max_eq_right hlo₂] at h + change decide (ux < lx ∨ uy < ly ∨ lx = ux ∧ ly = uy ∧ (lx, ly) ∈ allowed) = true at h + rw [decide_eq_true_eq] at h + rcases h with h | h | ⟨hx', hy', hm⟩ + · have h' : (ux : ℝ) < lx := by exact_mod_cast h + exact False.elim (by linarith [hx.1, hx.2]) + · have h' : (uy : ℝ) < ly := by exact_mod_cast h + exact False.elim (by linarith [hy.1, hy.2]) + · refine ⟨(lx, ly), hm, Prod.ext ?_ ?_⟩ + · change p.1 = (lx : ℝ) + rw [← hx'] at hx + exact le_antisymm hx.2 hx.1 + · change p.2 = (ly : ℝ) + rw [← hy'] at hy + exact le_antisymm hy.2 hy.1 + +/-- The finite square checker controls all real points of the route. -/ +theorem routeBoxContactCheck_sound {allowed : List Point} {r : Route} {c : Point} + {radius : ℕ} (h : routeBoxContactCheck allowed r c radius = true) + {p : ℝ × ℝ} (hp : p ∈ support r) (hc : inPointBox c radius p) : + ∃ q ∈ allowed, p = realPoint q := by + obtain ⟨e, he, hp⟩ := mem_support_iff_segmentPieces.mp hp + exact segment_box_contact_sound (List.all_eq_true.mp h e he) hp hc + +/-- Complete finite conditions on the annular connector family. -/ +def CollarCheck (s : PortAssignment) : Prop := + (collarRoutes s).length = 3 ∧ + ∀ i : Fin 3, + let r := (collarRoutes s).getD i [] + r.head? = some (collarOuterPort (s.side i)) ∧ + r.getLast? = some (collarInnerPort (collarReflected s) i) ∧ + IsOrthogonal r ∧ LocalGeometry.SimpleCheck r ∧ + (∀ p ∈ r, 0 ≤ p.1 ∧ p.1 ≤ 540000 ∧ 0 ≤ p.2 ∧ p.2 ≤ 540000) ∧ + (∀ p ∈ r.tail, 0 < p.1 ∧ p.1 < 540000 ∧ 0 < p.2 ∧ p.2 < 540000) ∧ + routeBoxContactCheck [collarInnerPort (collarReflected s) i] r + (270000, 270000) 90000 = true ∧ + ∀ j : Fin 3, i ≠ j → + routeContactCheck [] r ((collarRoutes s).getD j []) = true + +instance (s : PortAssignment) : Decidable (CollarCheck s) := by + unfold CollarCheck + refine @instDecidableAnd _ _ ?_ ?_ + all_goals infer_instance + +/-- Every ordered injection has its complete annular routing certificate. -/ +theorem collar_check : ∀ s : PortAssignment, s.Distinct → CollarCheck s := by + decide +kernel + +/-- Every collar route is continuously simple. -/ +theorem collar_isSimple {s : PortAssignment} (hs : s.Distinct) (i : Fin 3) : + IsSimple ((collarRoutes s).getD i []) := + LocalGeometry.isSimple_of_check ((collar_check s hs).2 i).2.2.2.1 + +/-- Distinct collar routes have disjoint full real supports. -/ +theorem collar_disjoint {s : PortAssignment} (hs : s.Distinct) {i j : Fin 3} (hij : i ≠ j) : + Disjoint (support ((collarRoutes s).getD i [])) (support ((collarRoutes s).getD j [])) := + disjoint_support_of_routeContactCheck (((collar_check s hs).2 i).2.2.2.2.2.2.2 j hij) + +/-- A collar route meets the closed inner template square only at its prescribed endpoint. -/ +theorem collar_core_contact {s : PortAssignment} (hs : s.Distinct) {i : Fin 3} + {p : ℝ × ℝ} (hp : p ∈ support ((collarRoutes s).getD i [])) + (hc : inPointBox (270000, 270000) 90000 p) : + p = realPoint (collarInnerPort (collarReflected s) i) := by + obtain ⟨q, hq, hpq⟩ := routeBoxContactCheck_sound + (((collar_check s hs).2 i).2.2.2.2.2.2.1) hp hc + exact List.mem_singleton.mp hq ▸ hpq + +/-- Each connector has at most seven segments and wire length at most 600000. -/ +theorem collar_route_costs : ∀ s : PortAssignment, s.Distinct → ∀ i : Fin 3, + ((collarRoutes s).getD i []).consecutivePairs.length ≤ 7 ∧ + wireLength ((collarRoutes s).getD i []) ≤ 600000 := by + decide +kernel + +/-- The entire three-connector family has wire length at most 1780000. -/ +theorem collar_total_wireLength : ∀ s : PortAssignment, s.Distinct → + ((collarRoutes s).map wireLength).sum ≤ 1780000 := by + decide +kernel + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollarData.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollarData.lean new file mode 100644 index 000000000..0012f792a --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollarData.lean @@ -0,0 +1,213 @@ +/- +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.ExactOne.Geometry + +/-! +# Finite collars for every ordered three-port assignment + +An inner square of side 180000 is centered inside a square of side 540000. Three disjoint +orthogonal routes connect the inner north/west/east accesses to any three distinct outer side +midpoints. A reflection of the inner layout selects the appropriate cyclic orientation. +The tables use only multiples of 10000 and have at most seven segments per route. + +## Main definitions + +* `PortAssignment`: three ordered sides, using left/right/bottom/top as 0/1/2/3. +* `collarReflected`, `collarRoutes`: the finite orientation and connector tables. +* `collarOuterPort`, `collarInnerPort`: prescribed exterior and interior endpoints. + +## References + +These finite annular routing tables are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Orthogonal + +/-- Three ordered outer sides; the geometry certificate assumes they are distinct. -/ +abbrev PortAssignment := Fin 4 × Fin 4 × Fin 4 + +/-- The three designated sides are distinct. -/ +def PortAssignment.Distinct (s : PortAssignment) : Prop := + s.1 ≠ s.2.1 ∧ s.1 ≠ s.2.2 ∧ s.2.1 ≠ s.2.2 + +instance (s : PortAssignment) : Decidable s.Distinct := + inferInstanceAs (Decidable (_ ∧ _)) + +/-- Side assigned to an indexed terminal. -/ +def PortAssignment.side (s : PortAssignment) (i : Fin 3) : Fin 4 := + if i = 0 then s.1 else if i = 1 then s.2.1 else s.2.2 + +/-- All 24 ordered injections into the four outer sides. -/ +def collarAssignments : List PortAssignment := + [(0, 1, 2), (0, 1, 3), (0, 2, 1), (0, 2, 3), (0, 3, 1), (0, 3, 2), + (1, 0, 2), (1, 0, 3), (1, 2, 0), (1, 2, 3), (1, 3, 0), (1, 3, 2), + (2, 0, 1), (2, 0, 3), (2, 1, 0), (2, 1, 3), (2, 3, 0), (2, 3, 1), + (3, 0, 1), (3, 0, 2), (3, 1, 0), (3, 1, 2), (3, 2, 0), (3, 2, 1)] + +/-- Finite reflection choices, in `collarAssignments` order. -/ +def collarReflections : List Bool := + [false, true, true, true, false, false, true, false, false, false, true, true, + false, false, true, true, true, false, true, true, false, false, false, true] + +/-- All connector tables, with routes oriented from the outer boundary to the inner square. -/ +def collarRouteTable : List (List Route) := + [ + [[(0, 270000), (160000, 270000), (160000, 260000), (170000, 260000), + (170000, 170000), (270000, 170000), (270000, 180000)], + [(540000, 270000), (380000, 270000), (380000, 280000), (370000, 280000), + (370000, 370000), (170000, 370000), (170000, 270000), (180000, 270000)], + [(270000, 0), (270000, 10000), (370000, 10000), (370000, 270000), + (360000, 270000)]], + [[(0, 270000), (10000, 270000), (10000, 170000), (270000, 170000), + (270000, 180000)], + [(540000, 270000), (360000, 270000)], + [(270000, 540000), (270000, 370000), (170000, 370000), (170000, 270000), + (180000, 270000)]], + [[(0, 270000), (10000, 270000), (10000, 170000), (270000, 170000), + (270000, 180000)], + [(270000, 0), (270000, 160000), (280000, 160000), (280000, 170000), + (370000, 170000), (370000, 270000), (360000, 270000)], + [(540000, 270000), (530000, 270000), (530000, 370000), (170000, 370000), + (170000, 270000), (180000, 270000)]], + [[(0, 270000), (160000, 270000), (160000, 260000), (170000, 260000), + (170000, 170000), (270000, 170000), (270000, 180000)], + [(270000, 0), (270000, 10000), (370000, 10000), (370000, 270000), + (360000, 270000)], + [(270000, 540000), (270000, 370000), (170000, 370000), (170000, 270000), + (180000, 270000)]], + [[(0, 270000), (160000, 270000), (160000, 260000), (170000, 260000), + (170000, 170000), (270000, 170000), (270000, 180000)], + [(270000, 540000), (270000, 530000), (170000, 530000), (170000, 270000), + (180000, 270000)], + [(540000, 270000), (360000, 270000)]], + [[(0, 270000), (10000, 270000), (10000, 170000), (270000, 170000), + (270000, 180000)], + [(270000, 540000), (270000, 530000), (170000, 530000), (170000, 270000), + (180000, 270000)], + [(270000, 0), (270000, 10000), (370000, 10000), (370000, 270000), + (360000, 270000)]], + [[(540000, 270000), (530000, 270000), (530000, 170000), (270000, 170000), + (270000, 180000)], + [(0, 270000), (160000, 270000), (160000, 280000), (170000, 280000), + (170000, 370000), (370000, 370000), (370000, 270000), (360000, 270000)], + [(270000, 0), (270000, 160000), (260000, 160000), (260000, 170000), + (170000, 170000), (170000, 270000), (180000, 270000)]], + [[(540000, 270000), (380000, 270000), (380000, 260000), (370000, 260000), + (370000, 170000), (270000, 170000), (270000, 180000)], + [(0, 270000), (180000, 270000)], + [(270000, 540000), (270000, 370000), (370000, 370000), (370000, 270000), + (360000, 270000)]], + [[(540000, 270000), (530000, 270000), (530000, 170000), (270000, 170000), + (270000, 180000)], + [(270000, 0), (270000, 160000), (260000, 160000), (260000, 170000), + (170000, 170000), (170000, 270000), (180000, 270000)], + [(0, 270000), (10000, 270000), (10000, 370000), (370000, 370000), + (370000, 270000), (360000, 270000)]], + [[(540000, 270000), (530000, 270000), (530000, 170000), (270000, 170000), + (270000, 180000)], + [(270000, 0), (270000, 160000), (260000, 160000), (260000, 170000), + (170000, 170000), (170000, 270000), (180000, 270000)], + [(270000, 540000), (270000, 370000), (370000, 370000), (370000, 270000), + (360000, 270000)]], + [[(540000, 270000), (530000, 270000), (530000, 170000), (270000, 170000), + (270000, 180000)], + [(270000, 540000), (270000, 530000), (370000, 530000), (370000, 270000), + (360000, 270000)], + [(0, 270000), (180000, 270000)]], + [[(540000, 270000), (380000, 270000), (380000, 260000), (370000, 260000), + (370000, 170000), (270000, 170000), (270000, 180000)], + [(270000, 540000), (270000, 370000), (370000, 370000), (370000, 270000), + (360000, 270000)], + [(270000, 0), (270000, 10000), (170000, 10000), (170000, 270000), + (180000, 270000)]], + [[(270000, 0), (270000, 180000)], + [(0, 270000), (180000, 270000)], + [(540000, 270000), (360000, 270000)]], + [[(270000, 0), (270000, 180000)], + [(0, 270000), (180000, 270000)], + [(270000, 540000), (270000, 530000), (370000, 530000), (370000, 270000), + (360000, 270000)]], + [[(270000, 0), (270000, 180000)], + [(540000, 270000), (360000, 270000)], + [(0, 270000), (180000, 270000)]], + [[(270000, 0), (270000, 180000)], + [(540000, 270000), (360000, 270000)], + [(270000, 540000), (270000, 530000), (170000, 530000), (170000, 270000), + (180000, 270000)]], + [[(270000, 0), (270000, 180000)], + [(270000, 540000), (270000, 530000), (370000, 530000), (370000, 270000), + (360000, 270000)], + [(0, 270000), (180000, 270000)]], + [[(270000, 0), (270000, 180000)], + [(270000, 540000), (270000, 370000), (170000, 370000), (170000, 270000), + (180000, 270000)], + [(540000, 270000), (360000, 270000)]], + [[(270000, 540000), (270000, 530000), (160000, 530000), (160000, 260000), + (170000, 260000), (170000, 170000), (270000, 170000), (270000, 180000)], + [(0, 270000), (10000, 270000), (10000, 160000), (280000, 160000), + (280000, 170000), (370000, 170000), (370000, 270000), (360000, 270000)], + [(540000, 270000), (530000, 270000), (530000, 370000), (170000, 370000), + (170000, 270000), (180000, 270000)]], + [[(270000, 540000), (270000, 380000), (380000, 380000), (380000, 260000), + (370000, 260000), (370000, 170000), (270000, 170000), (270000, 180000)], + [(0, 270000), (10000, 270000), (10000, 370000), (370000, 370000), + (370000, 270000), (360000, 270000)], + [(270000, 0), (270000, 10000), (170000, 10000), (170000, 270000), + (180000, 270000)]], + [[(270000, 540000), (270000, 380000), (380000, 380000), (380000, 170000), + (270000, 170000), (270000, 180000)], + [(540000, 270000), (530000, 270000), (530000, 160000), (260000, 160000), + (260000, 170000), (170000, 170000), (170000, 270000), (180000, 270000)], + [(0, 270000), (10000, 270000), (10000, 370000), (370000, 370000), + (370000, 270000), (360000, 270000)]], + [[(270000, 540000), (270000, 530000), (160000, 530000), (160000, 260000), + (170000, 260000), (170000, 170000), (270000, 170000), (270000, 180000)], + [(540000, 270000), (530000, 270000), (530000, 370000), (170000, 370000), + (170000, 270000), (180000, 270000)], + [(270000, 0), (270000, 10000), (370000, 10000), (370000, 270000), + (360000, 270000)]], + [[(270000, 540000), (270000, 380000), (380000, 380000), (380000, 170000), + (270000, 170000), (270000, 180000)], + [(270000, 0), (270000, 10000), (170000, 10000), (170000, 270000), + (180000, 270000)], + [(0, 270000), (160000, 270000), (160000, 280000), (170000, 280000), + (170000, 370000), (370000, 370000), (370000, 270000), (360000, 270000)]], + [[(270000, 540000), (270000, 380000), (160000, 380000), (160000, 170000), + (270000, 170000), (270000, 180000)], + [(270000, 0), (270000, 10000), (370000, 10000), (370000, 270000), + (360000, 270000)], + [(540000, 270000), (530000, 270000), (530000, 370000), (170000, 370000), + (170000, 270000), (180000, 270000)]] + ] + +/-- The reflection required for the selected ordered assignment. -/ +def collarReflected (s : PortAssignment) : Bool := + collarReflections.getD (collarAssignments.idxOf s) false + +/-- Three outer-to-inner collar routes. Invalid repeated-side assignments use an empty default. +-/ +def collarRoutes (s : PortAssignment) : List Route := + collarRouteTable.getD (collarAssignments.idxOf s) [] + +/-- Exterior midpoint in left/right/bottom/top order. -/ +def collarOuterPort (s : Fin 4) : Point := + if s = 0 then (0, 270000) else if s = 1 then (540000, 270000) + else if s = 2 then (270000, 0) else (270000, 540000) + +/-- The translated inner port, including the selected horizontal reflection. -/ +def collarInnerPort (reflected : Bool) (i : Fin 3) : Point := + if i = 0 then (270000, 180000) + else if (i = 1) != reflected then (180000, 270000) else (360000, 270000) + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollarGlue.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollarGlue.lean new file mode 100644 index 000000000..586aaff7d --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryCollarGlue.lean @@ -0,0 +1,361 @@ +/- +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.ExactOne.GeometryCollar +public import Complexity.Foundations.Geometry.Orthogonal.Join +public import Complexity.Foundations.Geometry.Orthogonal.Mirror +public import Complexity.Foundations.Geometry.Orthogonal.Reverse +public import Complexity.Foundations.Geometry.Orthogonal.Macrocell +import Mathlib.Tactic.NormNum + +/-! +# Gluing clause templates to ordered-port collars + +Templates of side 180000 are translated, and optionally reflected, into the central square +of the side-540000 collar. A clause-to-inner-boundary prefix then extends along its reversed +collar route. The continuous certificates compose through their unique common endpoint. + +## Main definitions + +* `collarPoint`, `collarRoute`: the common affine placement of an inner template. +* `collarPrefixRoute`: a placed local prefix followed by its inner-to-outer collar. +* `collaredPrefixGeometry`: the actual finite placed template and its extended incidences. + +## Main results + +* `isSimple_collarPrefixRoute`, `isOrthogonal_collarPrefixRoute`. +* `support_collarPrefixRoute`, `wireLength_collarPrefixRoute`. + +## References + +These finite local-placement and gluing constructions are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Orthogonal + +/-- Translation part of the reflected or unreflected inner-template placement. -/ +def collarOffset (reflected : Bool) : Point := + (if reflected then 360000 else 180000, 180000) + +/-- Place a point of the side-180000 template inside the central collar square. -/ +def collarPoint (reflected : Bool) (p : Point) : Point := + (if reflected then mirrorPoint p else p) + collarOffset reflected + +/-- The corresponding placement in the real plane. -/ +def collarReal (reflected : Bool) (p : ℝ × ℝ) : ℝ × ℝ := + (if reflected then mirrorReal p else p) + realPoint (collarOffset reflected) + +/-- Place an entire local route. -/ +def collarRoute (reflected : Bool) (r : Route) : Route := + translate (collarOffset reflected) (if reflected then mirrorRoute r else r) + +/-- Route placement is pointwise placement of the finite list. -/ +theorem collarRoute_eq_map (reflected : Bool) (r : Route) : + collarRoute reflected r = r.map (collarPoint reflected) := by + cases reflected <;> simp [collarRoute, collarPoint, translate, mirrorRoute, + List.map_map, Function.comp_def] + +/-- Placement commutes with inclusion of the integer lattice. -/ +theorem realPoint_collarPoint (reflected : Bool) (p : Point) : + realPoint (collarPoint reflected p) = collarReal reflected (realPoint p) := by + cases reflected <;> + simp [collarPoint, collarReal, realPoint_add, realPoint_mirrorPoint] + +/-- The common affine placement does not identify real points. -/ +theorem collarReal_injective (reflected : Bool) : Function.Injective (collarReal reflected) := by + intro p q h + cases reflected <;> simpa [collarReal, mirrorReal, Prod.ext_iff] using h + +/-- The common affine placement does not identify integer vertices. -/ +theorem collarPoint_injective (reflected : Bool) : Function.Injective (collarPoint reflected) := by + intro p q h + apply realPoint_injective + apply collarReal_injective reflected + simpa only [← realPoint_collarPoint] using congrArg realPoint h + +/-- Placement maps the full continuous support. -/ +theorem support_collarRoute (reflected : Bool) (r : Route) : + support (collarRoute reflected r) = collarReal reflected '' support r := by + unfold collarRoute + rw [support_translate] + cases reflected + · rfl + · change (fun p => p + realPoint (collarOffset true)) '' support (mirrorRoute r) = _ + rw [support_mirrorRoute, Set.image_image] + rfl + +/-- Membership in a placed support is exactly placed membership. -/ +theorem mem_support_collarRoute_iff (reflected : Bool) (r : Route) (p : ℝ × ℝ) : + collarReal reflected p ∈ support (collarRoute reflected r) ↔ p ∈ support r := by + rw [support_collarRoute] + exact (collarReal_injective reflected).mem_set_image + +/-- Placement preserves proper orthogonality. -/ +theorem isOrthogonal_collarRoute (reflected : Bool) {r : Route} (hr : IsOrthogonal r) : + IsOrthogonal (collarRoute reflected r) := by + unfold collarRoute + rw [isOrthogonal_translate_iff] + cases reflected + · exact hr + · exact (isOrthogonal_mirrorRoute_iff r).2 hr + +/-- Placement preserves continuous simplicity. -/ +theorem isSimple_collarRoute (reflected : Bool) {r : Route} (hr : IsSimple r) : + IsSimple (collarRoute reflected r) := by + unfold collarRoute + rw [isSimple_translate_iff] + cases reflected + · exact hr + · exact (isSimple_mirrorRoute_iff r).2 hr + +/-- Placement preserves total Manhattan wire length. -/ +theorem wireLength_collarRoute (reflected : Bool) (r : Route) : + wireLength (collarRoute reflected r) = wireLength r := by + unfold collarRoute + rw [wireLength_translate] + cases reflected + · rfl + · exact wireLength_mirrorRoute r + +/-- Placement preserves the listed segment count. -/ +theorem consecutivePairs_length_collarRoute (reflected : Bool) (r : Route) : + (collarRoute reflected r).consecutivePairs.length = r.consecutivePairs.length := by + simp only [collarRoute_eq_map, List.consecutivePairs, List.length_zip, List.length_tail, + List.length_map] + +/-- The fixed north/west/east ports of an unplaced inner template. -/ +def innerTemplatePort (i : Fin 3) : Point := + if i = 0 then (90000, 0) else if i = 1 then (0, 90000) else (180000, 90000) + +/-- Placement identifies the inner template ports with the collar's inner endpoints. -/ +theorem collarPoint_innerTemplatePort : ∀ reflected i, + collarPoint reflected (innerTemplatePort i) = collarInnerPort reflected i := by + decide +kernel + +/-- Pointwise box bounds extend to the full real polygonal support. -/ +theorem support_inPointBox_of_points {r : Route} {c : Point} {radius : ℕ} + (hr : ∀ q ∈ r, inPointBox c radius (realPoint q)) {p : ℝ × ℝ} + (hp : p ∈ support r) : inPointBox c radius p := by + rcases hp with ⟨q, hq, rfl⟩ | ⟨e, he, hp⟩ + · exact hr q hq + · have hm := List.of_mem_zip he + have ha := hr e.1 hm.1 + have hb := hr e.2 (List.mem_of_mem_tail hm.2) + have hc := mem_segment_bounds hp + change (min (e.1.1 : ℝ) (e.2.1 : ℝ) ≤ p.1 ∧ p.1 ≤ max (e.1.1 : ℝ) (e.2.1 : ℝ)) ∧ + (min (e.1.2 : ℝ) (e.2.2 : ℝ) ≤ p.2 ∧ p.2 ≤ max (e.1.2 : ℝ) (e.2.2 : ℝ)) at hc + exact ⟨(le_min ha.1 hb.1).trans hc.1.1, hc.1.2.trans (max_le ha.2.1 hb.2.1), + (le_min ha.2.2.1 hb.2.2.1).trans hc.2.1, + hc.2.2.trans (max_le ha.2.2.2 hb.2.2.2)⟩ + +/-- Placed points of the original template lie in the collar's closed inner square. -/ +theorem collarPoint_in_innerBox (reflected : Bool) {q : Point} + (hq : 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000) : + inPointBox (270000, 270000) 90000 (realPoint (collarPoint reflected q)) := by + have hi : 180000 ≤ (collarPoint reflected q).1 ∧ + (collarPoint reflected q).1 ≤ 360000 ∧ + 180000 ≤ (collarPoint reflected q).2 ∧ (collarPoint reflected q).2 ≤ 360000 := by + cases reflected <;> dsimp [collarPoint, collarOffset, mirrorPoint] <;> omega + norm_num [inPointBox, realPoint] + exact_mod_cast hi + +/-- A placed local route stays inside the collar's closed inner square. -/ +theorem collarRoute_in_innerBox (reflected : Bool) {r : Route} + (hr : ∀ q ∈ r, 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000) + {p : ℝ × ℝ} (hp : p ∈ support (collarRoute reflected r)) : + inPointBox (270000, 270000) 90000 p := by + apply support_inPointBox_of_points (r := collarRoute reflected r) ?_ hp + intro q hq + rw [collarRoute_eq_map] at hq + obtain ⟨q, hqr, rfl⟩ := List.mem_map.mp hq + exact collarPoint_in_innerBox reflected (hr q hqr) + +/-- Extend an inner clause-to-boundary prefix along the reversed annular connector. -/ +def collarPrefixRoute (s : PortAssignment) (i : Fin 3) (r : Route) : Route := + collarRoute (collarReflected s) r ++ ((collarRoutes s).getD i []).reverse.tail + +private theorem collarPrefix_join_endpoints {s : PortAssignment} (hs : s.Distinct) + {i : Fin 3} {r : Route} (hr : r.getLast? = some (innerTemplatePort i)) : + (collarRoute (collarReflected s) r).getLast? = + some (collarInnerPort (collarReflected s) i) ∧ + ((collarRoutes s).getD i []).reverse.head? = + some (collarInnerPort (collarReflected s) i) := by + constructor + · rw [collarRoute_eq_map, List.getLast?_map, hr] + exact congrArg some (collarPoint_innerTemplatePort _ _) + · simpa only [List.head?_reverse] using ((collar_check s hs).2 i).2.1 + +/-- Extending a local prefix preserves proper orthogonality. -/ +theorem isOrthogonal_collarPrefixRoute {s : PortAssignment} (hs : s.Distinct) + {i : Fin 3} {r : Route} (hr : r.getLast? = some (innerTemplatePort i)) + (ho : IsOrthogonal r) : IsOrthogonal (collarPrefixRoute s i r) := by + have he := collarPrefix_join_endpoints hs hr + exact isOrthogonal_join he.1 he.2 (isOrthogonal_collarRoute _ ho) + ((isOrthogonal_reverse_iff _).2 (((collar_check s hs).2 i).2.2.1)) + +/-- The support of an extended prefix is precisely the union of its placed local support +and its annular connector. -/ +theorem support_collarPrefixRoute {s : PortAssignment} (hs : s.Distinct) + {i : Fin 3} {r : Route} (hr : r.getLast? = some (innerTemplatePort i)) : + support (collarPrefixRoute s i r) = support (collarRoute (collarReflected s) r) ∪ + support ((collarRoutes s).getD i []) := by + have he := collarPrefix_join_endpoints hs hr + rw [collarPrefixRoute, support_join he.1 he.2, support_reverse] + +/-- Local prefix and annular connector glue to a continuously simple route. -/ +theorem isSimple_collarPrefixRoute {s : PortAssignment} (hs : s.Distinct) + {i : Fin 3} {r : Route} (hr : r.getLast? = some (innerTemplatePort i)) + (hsr : IsSimple r) + (hb : ∀ q ∈ r, 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000) : + IsSimple (collarPrefixRoute s i r) := by + have he := collarPrefix_join_endpoints hs hr + refine isSimple_join he.1 he.2 (isSimple_collarRoute _ hsr) + ((isSimple_reverse_iff _).2 (collar_isSimple hs i)) ?_ + intro p hp hq + rw [support_reverse] at hq + exact collar_core_contact hs hq (collarRoute_in_innerBox _ hb hp) + +/-- Extending a prefix adds exactly the annular connector's wire length. -/ +theorem wireLength_collarPrefixRoute {s : PortAssignment} (hs : s.Distinct) + {i : Fin 3} {r : Route} (hr : r.getLast? = some (innerTemplatePort i)) : + wireLength (collarPrefixRoute s i r) = + wireLength r + wireLength ((collarRoutes s).getD i []) := by + have he := collarPrefix_join_endpoints hs hr + rw [collarPrefixRoute, wireLength_join he.1 he.2, wireLength_collarRoute, wireLength_reverse] + +/-- Extending a prefix adds exactly the annular connector's listed segments. -/ +theorem consecutivePairs_length_collarPrefixRoute {s : PortAssignment} (hs : s.Distinct) + {i : Fin 3} {r : Route} (hr : r.getLast? = some (innerTemplatePort i)) : + (collarPrefixRoute s i r).consecutivePairs.length = + r.consecutivePairs.length + ((collarRoutes s).getD i []).consecutivePairs.length := by + have he := collarPrefix_join_endpoints hs hr + rw [collarPrefixRoute, consecutivePairs_join he.1 he.2, List.length_append, + consecutivePairs_length_collarRoute, consecutivePairs_length_reverse] + +/-- A placed core route avoiding an inner port is disjoint from that port's annular connector. +-/ +theorem disjoint_collarRoute_connector {s : PortAssignment} (hs : s.Distinct) + {i : Fin 3} {r : Route} + (hb : ∀ q ∈ r, 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000) + (hi : realPoint (innerTemplatePort i) ∉ support r) : + Disjoint (support (collarRoute (collarReflected s) r)) + (support ((collarRoutes s).getD i [])) := by + rw [Set.disjoint_left] + intro p hp hq + have hport := collar_core_contact hs hq (collarRoute_in_innerBox _ hb hp) + have he : collarReal (collarReflected s) (realPoint (innerTemplatePort i)) = + realPoint (collarInnerPort (collarReflected s) i) := by + rw [← realPoint_collarPoint, collarPoint_innerTemplatePort] + rw [hport, ← he] at hp + exact hi ((mem_support_collarRoute_iff _ r _).1 hp) + +/-- Intersections between two distinct extended prefixes come entirely from intersections +of their original local routes. The two local routes must avoid each other's inner port. -/ +theorem collarPrefixRoute_contact {s : PortAssignment} (hs : s.Distinct) + {i j : Fin 3} (hij : i ≠ j) {r t : Route} + (hr : r.getLast? = some (innerTemplatePort i)) + (ht : t.getLast? = some (innerTemplatePort j)) + (hbr : ∀ q ∈ r, 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000) + (hbt : ∀ q ∈ t, 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000) + (hri : realPoint (innerTemplatePort j) ∉ support r) + (htj : realPoint (innerTemplatePort i) ∉ support t) + {p : ℝ × ℝ} (hp : p ∈ support (collarPrefixRoute s i r)) + (hq : p ∈ support (collarPrefixRoute s j t)) : + ∃ q ∈ support r, q ∈ support t ∧ p = collarReal (collarReflected s) q := by + rw [support_collarPrefixRoute hs hr] at hp + rw [support_collarPrefixRoute hs ht] at hq + rcases hp with hp | hp <;> rcases hq with hq | hq + · rw [support_collarRoute] at hp hq + obtain ⟨x, hx, hxp⟩ := hp + obtain ⟨y, hy, hyp⟩ := hq + have hxy := collarReal_injective (collarReflected s) (hxp.trans hyp.symm) + subst y + exact ⟨x, hx, hy, hxp.symm⟩ + · exact False.elim (Set.disjoint_left.mp (disjoint_collarRoute_connector hs hbr hri) hp hq) + · exact False.elim (Set.disjoint_left.mp (disjoint_collarRoute_connector hs hbt htj) hq hp) + · exact False.elim (Set.disjoint_left.mp (collar_disjoint hs hij) hp hq) + +/-- An extended prefix avoids a placed inner-template vertex when its local route avoids +that vertex and it is not the prefix's own boundary terminal. -/ +theorem collarPrefixRoute_not_mem_corePoint {s : PortAssignment} (hs : s.Distinct) + {i : Fin 3} {r : Route} (hr : r.getLast? = some (innerTemplatePort i)) + {q : Point} (hq : 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000) + (hqi : q ≠ innerTemplatePort i) (hn : realPoint q ∉ support r) : + realPoint (collarPoint (collarReflected s) q) ∉ support (collarPrefixRoute s i r) := by + intro hp + rw [support_collarPrefixRoute hs hr] at hp + rcases hp with hp | hp + · rw [realPoint_collarPoint] at hp + exact hn ((mem_support_collarRoute_iff _ _ _).1 hp) + · have he := collar_core_contact hs hp (collarPoint_in_innerBox _ hq) + rw [← collarPoint_innerTemplatePort] at he + exact hqi (collarPoint_injective _ (realPoint_injective he)) + +/-- The perimeter of the outer collar square. -/ +def collarBoundaryRoute : Route := + [(0, 0), (540000, 0), (540000, 540000), (0, 540000), (0, 0)] + +private theorem collar_boundary_check : ∀ s : PortAssignment, s.Distinct → ∀ i : Fin 3, + routeContactCheck [collarOuterPort (s.side i)] ((collarRoutes s).getD i []) + collarBoundaryRoute = true := by + decide +kernel + +private theorem collar_boundary_inner_check : + routeBoxContactCheck [] collarBoundaryRoute (270000, 270000) 90000 = true := by + decide +kernel + +/-- The collar's full support meets its outer perimeter only at its own assigned port. -/ +theorem collar_outer_boundary_contact {s : PortAssignment} (hs : s.Distinct) {i : Fin 3} + {p : ℝ × ℝ} (hp : p ∈ support ((collarRoutes s).getD i [])) + (hb : p ∈ support collarBoundaryRoute) : p = realPoint (collarOuterPort (s.side i)) := by + obtain ⟨q, hq, hpq⟩ := routeContactCheck_sound (collar_boundary_check s hs i) hp hb + exact List.mem_singleton.mp hq ▸ hpq + +/-- The full support of an extended prefix meets the outer perimeter only at its assigned +source port. -/ +theorem collarPrefixRoute_outer_boundary_contact {s : PortAssignment} (hs : s.Distinct) + {i : Fin 3} {r : Route} (hr : r.getLast? = some (innerTemplatePort i)) + (hbr : ∀ q ∈ r, 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000) + {p : ℝ × ℝ} (hp : p ∈ support (collarPrefixRoute s i r)) + (hb : p ∈ support collarBoundaryRoute) : p = realPoint (collarOuterPort (s.side i)) := by + rw [support_collarPrefixRoute hs hr] at hp + rcases hp with hp | hp + · obtain ⟨q, hq, _⟩ := routeBoxContactCheck_sound collar_boundary_inner_check hb + (collarRoute_in_innerBox _ hbr hp) + exact False.elim (List.not_mem_nil hq) + · exact collar_outer_boundary_contact hs hp hb + +/-- Position of an exposed variable in the ordered terminal list, with an irrelevant default +for malformed local data containing more than three terminals. -/ +def terminalIndex (g : LocalGeometry) (v : ℕ) : Fin 3 := + ⟨g.terminalVariables.idxOf v % 3, Nat.mod_lt _ (by decide)⟩ + +/-- Place a finite prefix template and extend its source incidences to arbitrary ordered +outer sides. Source ghost positions follow those same outer ports; actual internal vertices +and all clause positions receive the common affine placement. -/ +def collaredPrefixGeometry (g : LocalGeometry) (s : PortAssignment) : LocalGeometry where + size := 540000 + variablePositions := g.variablePositions.zipIdx.map fun pi => + if pi.2 ∈ g.terminalVariables then collarOuterPort (s.side (terminalIndex g pi.2)) + else collarPoint (collarReflected s) pi.1 + clausePositions := g.clausePositions.map (collarPoint (collarReflected s)) + incidenceRoutes := g.incidenceRoutes.map fun e => + (e.1, e.2.1, + if e.2.1 ∈ g.terminalVariables then collarPrefixRoute s (terminalIndex g e.2.1) e.2.2 + else collarRoute (collarReflected s) e.2.2) + terminalVariables := g.terminalVariables + boundaryPorts := g.terminalVariables.zipIdx.map fun vi => + collarOuterPort (s.side ⟨vi.2 % 3, Nat.mod_lt _ (by decide)⟩) + accessRoutes := [] + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryData.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryData.lean new file mode 100644 index 000000000..58c1300b9 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryData.lean @@ -0,0 +1,148 @@ +/- +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 + +/-! +# Local orthogonal drawings for parsimonious exact-one gadgets + +The three fixed drawings fit in `[0,180]²`. Their core vertices and routes lie in +`[20,160]²`; access routes run from designated side midpoints to their terminals. +The unsigned incidence signatures deliberately leave clause semantics to the logical gadgets. + +## Main definitions + +* `LocalGeometry`: finite vertex, incidence, and access data for a local replacement. +* `disjunctionGeometry`: the four-clause parsimonious disjunction replacement. +* `falseGeometry`: the three-clause positive gadget with a forced-false exterior terminal. +* `complementGeometry`: a positive two-terminal gadget forcing opposite values. + +## References + +The incidence graphs are from [HuntMaratheRadhakrishnanStearns1998, Theorem 3.8, +Figures 3.4–3.5 and Lemma 3.6]. The orthogonal coordinates and routes are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Orthogonal + +/-- An incidence records its clause index, variable index, and clause-to-variable route. -/ +abbrev LocalIncidence := ℕ × ℕ × Route + +/-- Finite local replacement data. Variables and clauses have separate zero-based indices. +`terminalVariables`, `boundaryPorts`, and `accessRoutes` have corresponding orders. -/ +structure LocalGeometry where + /-- Common square side length; the elementary templates use 180. -/ + size : ℕ := 180 + /-- Positions in variable-index order. -/ + variablePositions : List Point + /-- Positions in clause-index order. -/ + clausePositions : List Point + /-- Routes with their incident clause and variable indices. -/ + incidenceRoutes : List LocalIncidence + /-- Variables exposed to the exterior. -/ + terminalVariables : List ℕ + /-- Exterior endpoints of the access routes. -/ + boundaryPorts : List Point + /-- Boundary-to-terminal routes. -/ + accessRoutes : List Route + +namespace LocalGeometry + +/-- Indexed variable position; valid incidence indices are certified separately. -/ +def variablePosition (g : LocalGeometry) (v : ℕ) : Point := + g.variablePositions.getD v (0, 0) + +/-- Indexed clause position; valid incidence indices are certified separately. -/ +def clausePosition (g : LocalGeometry) (i : ℕ) : Point := + g.clausePositions.getD i (0, 0) + +/-- Permitted contacts of two incidence routes: their shared graph endpoints. -/ +def commonEndpoints (g : LocalGeometry) (e f : LocalIncidence) : List Point := + (if e.1 = f.1 then [g.clausePosition e.1] else []) ++ + (if e.2.1 = f.2.1 then [g.variablePosition e.2.1] else []) + +end LocalGeometry + +/-- The four-clause disjunction gadget. Variables are `p,q,r,u,v,w,t,x` in that order; +the incidence signature is `[[p,u,v],[q,u,w],[v,w,t],[r,v,x]]`. +The boundary terminals are `p,q,r`, with north, west, and east accesses. -/ +def disjunctionGeometry : LocalGeometry where + variablePositions := + [(20, 20), (20, 100), (140, 20), (40, 60), + (100, 40), (70, 100), (100, 120), (160, 40)] + clausePositions := [(40, 40), (40, 100), (100, 100), (140, 40)] + incidenceRoutes := + [(0, 0, [(40, 40), (20, 40), (20, 20)]), + (0, 3, [(40, 40), (40, 60)]), + (0, 4, [(40, 40), (100, 40)]), + (1, 1, [(40, 100), (20, 100)]), + (1, 3, [(40, 100), (40, 60)]), + (1, 5, [(40, 100), (70, 100)]), + (2, 4, [(100, 100), (100, 40)]), + (2, 5, [(100, 100), (70, 100)]), + (2, 6, [(100, 100), (100, 120)]), + (3, 2, [(140, 40), (140, 20)]), + (3, 4, [(140, 40), (100, 40)]), + (3, 7, [(140, 40), (160, 40)])] + terminalVariables := [0, 1, 2] + boundaryPorts := [(90, 0), (0, 90), (180, 90)] + accessRoutes := + [[(90, 0), (90, 10), (20, 10), (20, 20)], + [(0, 90), (10, 90), (10, 100), (20, 100)], + [(180, 90), (170, 90), (170, 20), (140, 20)]] + +/-- The positive forced-false gadget. Variables are `c,d,e,f`; the three clauses have +signature `[[c,d,e],[c,e,f],[d,e,f]]`. The terminal `c` has a north access. -/ +def falseGeometry : LocalGeometry where + variablePositions := [(80, 20), (20, 100), (80, 60), (140, 100)] + clausePositions := [(40, 40), (120, 40), (80, 120)] + incidenceRoutes := + [(0, 0, [(40, 40), (40, 20), (80, 20)]), + (0, 1, [(40, 40), (20, 40), (20, 100)]), + (0, 2, [(40, 40), (40, 60), (80, 60)]), + (1, 0, [(120, 40), (120, 20), (80, 20)]), + (1, 2, [(120, 40), (120, 60), (80, 60)]), + (1, 3, [(120, 40), (140, 40), (140, 100)]), + (2, 1, [(80, 120), (20, 120), (20, 100)]), + (2, 2, [(80, 120), (80, 60)]), + (2, 3, [(80, 120), (140, 120), (140, 100)])] + terminalVariables := [0] + boundaryPorts := [(80, 0)] + accessRoutes := [[(80, 0), (80, 20)]] + +/-- A positive complement gadget: one clause on `p,q,c` followed by the forced-false +gadget on `c,d,e,f`. The terminals `p,q` have west and east accesses. -/ +def complementGeometry : LocalGeometry where + variablePositions := + [(20, 20), (140, 20), (80, 60), (20, 140), (80, 100), (140, 140)] + clausePositions := [(80, 20), (40, 80), (120, 80), (80, 160)] + incidenceRoutes := + [(0, 0, [(80, 20), (20, 20)]), + (0, 1, [(80, 20), (140, 20)]), + (0, 2, [(80, 20), (80, 60)]), + (1, 2, [(40, 80), (40, 60), (80, 60)]), + (1, 3, [(40, 80), (20, 80), (20, 140)]), + (1, 4, [(40, 80), (40, 100), (80, 100)]), + (2, 2, [(120, 80), (120, 60), (80, 60)]), + (2, 4, [(120, 80), (120, 100), (80, 100)]), + (2, 5, [(120, 80), (140, 80), (140, 140)]), + (3, 3, [(80, 160), (20, 160), (20, 140)]), + (3, 4, [(80, 160), (80, 100)]), + (3, 5, [(80, 160), (140, 160), (140, 140)])] + terminalVariables := [0, 1] + boundaryPorts := [(0, 90), (180, 90)] + accessRoutes := + [[(0, 90), (10, 90), (10, 20), (20, 20)], + [(180, 90), (170, 90), (170, 20), (140, 20)]] + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryPrefix.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryPrefix.lean new file mode 100644 index 000000000..fbd09b417 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryPrefix.lean @@ -0,0 +1,169 @@ +/- +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.ExactOne.Geometry +public import Complexity.Foundations.Geometry.Orthogonal.Subdivision + +/-! +# Source-boundary prefixes for local clause replacements + +Each source placeholder is moved from its private local position to the corresponding boundary +port. Its sole incidence is extended through the reversed access route. The old placeholder is +then an ordinary interior bend, rather than a graph vertex. The finite certificates check the +actual resulting prefixes, including simplicity and the restriction that a route can touch the +box boundary only at its own variable endpoint. + +## Main definitions + +* `LocalGeometry.toPrefix`: move source placeholders to boundary ports and extend their incidences. +* `LocalGeometry.PrefixCheck`: finite continuous certificates for such route prefixes. + +## Main results + +* `LocalGeometry.toPrefix_boundary_contact`: only the variable endpoint can touch the boundary. + +## References + +These are original geometric certificates for the cited exact-one clause constructions. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Orthogonal + +namespace LocalGeometry + +/-- Check only consecutive segment pairs for two proper routes. Listed points already belong +to such segments, so including their separate degenerate pieces would duplicate this work. -/ +def segmentRouteContactCheck (allowed : List Point) (r s : Route) : Bool := + r.consecutivePairs.all fun e => s.consecutivePairs.all fun f => + segmentContactCheck allowed e.1 e.2 f.1 f.2 + +/-- Consecutive-segment checking controls the full real support of proper routes. -/ +theorem segmentRouteContactCheck_sound {allowed : List Point} {r s : Route} + (hr : 2 ≤ r.length) (hs : 2 ≤ s.length) + (h : segmentRouteContactCheck allowed r s = true) {p : ℝ × ℝ} + (hp : p ∈ support r) (hq : p ∈ support s) : + ∃ q ∈ allowed, p = realPoint q := by + obtain ⟨e, he, hp⟩ := mem_support_segment hr hp + obtain ⟨f, hf, hq⟩ := mem_support_segment hs hq + exact segmentContactCheck_sound + (List.all_eq_true.mp (List.all_eq_true.mp h e he) f hf) hp hq + +/-- The closed polygonal boundary of a local cell. -/ +def boundaryRoute (g : LocalGeometry) : Route := + [(0, 0), (g.size, 0), (g.size, g.size), (0, g.size), (0, 0)] + +/-- Move each source placeholder to its boundary port and extend its unique incidence through +the old placeholder position. `terminalVariables` retains the boundary placeholders' indices. -/ +def toPrefix (g : LocalGeometry) : LocalGeometry where + size := g.size + variablePositions := g.variablePositions.zipIdx.map fun (p, i) => + if i ∈ g.terminalVariables then + g.boundaryPorts.getD (g.terminalVariables.idxOf i) (0, 0) else p + clausePositions := g.clausePositions + incidenceRoutes := g.incidenceRoutes.map fun e => + (e.1, e.2.1, if e.2.1 ∈ g.terminalVariables then + e.2.2 ++ (g.accessRoutes.getD (g.terminalVariables.idxOf e.2.1) []).reverse.tail + else e.2.2) + terminalVariables := g.terminalVariables + boundaryPorts := g.boundaryPorts + accessRoutes := [] + +/-- A finite certificate for local incidence routes whose source ends lie on the cell boundary. +Full route contacts, nonincident vertices and the complete cell boundary are checked. -/ +def PrefixCheck (g : LocalGeometry) : Prop := + (g.variablePositions ++ g.clausePositions).Nodup ∧ + (g.incidenceRoutes.map fun e => (e.1, e.2.1)).Nodup ∧ + (∀ e ∈ g.incidenceRoutes, + e.1 < g.clausePositions.length ∧ e.2.1 < g.variablePositions.length ∧ + e.2.2.head? = some (g.clausePosition e.1) ∧ + e.2.2.getLast? = some (g.variablePosition e.2.1) ∧ + IsOrthogonal e.2.2 ∧ SimpleCheck e.2.2 ∧ 2 ≤ e.2.2.length ∧ + (∀ p ∈ e.2.2, 0 ≤ p.1 ∧ p.1 ≤ g.size ∧ 0 ≤ p.2 ∧ p.2 ≤ g.size) ∧ + ∀ p ∈ e.2.2.dropLast, 0 < p.1 ∧ p.1 < g.size ∧ 0 < p.2 ∧ p.2 < g.size) ∧ + (∀ e ∈ g.incidenceRoutes, ∀ f ∈ g.incidenceRoutes, e ≠ f → + segmentRouteContactCheck (g.commonEndpoints e f) e.2.2 f.2.2 = true) ∧ + (∀ e ∈ g.incidenceRoutes, ∀ v : Fin g.variablePositions.length, v.val ≠ e.2.1 → + routeContactCheck [] e.2.2 [g.variablePosition v] = true) ∧ + (∀ e ∈ g.incidenceRoutes, ∀ i : Fin g.clausePositions.length, i.val ≠ e.1 → + routeContactCheck [] e.2.2 [g.clausePosition i] = true) ∧ + ∀ e ∈ g.incidenceRoutes, + routeContactCheck [g.variablePosition e.2.1] e.2.2 g.boundaryRoute = true + +instance (g : LocalGeometry) : Decidable (PrefixCheck g) := by + unfold PrefixCheck + repeat' refine @instDecidableAnd _ _ ?_ ?_ + all_goals infer_instance + +/-- Every checked prefix has its prescribed clause and variable endpoints. -/ +theorem prefix_incidence_endpoints {g : LocalGeometry} (h : PrefixCheck g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) : + e.1 < g.clausePositions.length ∧ e.2.1 < g.variablePositions.length ∧ + e.2.2.head? = some (g.clausePosition e.1) ∧ + e.2.2.getLast? = some (g.variablePosition e.2.1) := by + have h' := h.2.2.1 e he + exact ⟨h'.1, h'.2.1, h'.2.2.1, h'.2.2.2.1⟩ + +/-- Every checked prefix is properly orthogonal. -/ +theorem prefix_incidence_orthogonal {g : LocalGeometry} (h : PrefixCheck g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) : IsOrthogonal e.2.2 := + (h.2.2.1 e he).2.2.2.2.1 + +/-- Every checked prefix is simple on its full continuous support. -/ +theorem prefix_incidence_isSimple {g : LocalGeometry} (h : PrefixCheck g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) : IsSimple e.2.2 := + isSimple_of_check ((h.2.2.1 e he).2.2.2.2.2.1) + +/-- Every point listed in a checked prefix lies in its closed cell. -/ +theorem prefix_incidence_bounds {g : LocalGeometry} (h : PrefixCheck g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) : + ∀ p ∈ e.2.2, 0 ≤ p.1 ∧ p.1 ≤ g.size ∧ 0 ≤ p.2 ∧ p.2 ≤ g.size := + (h.2.2.1 e he).2.2.2.2.2.2.2.1 + +/-- Every checked prefix has at least one proper consecutive segment. -/ +theorem prefix_incidence_length {g : LocalGeometry} (h : PrefixCheck g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) : 2 ≤ e.2.2.length := + (h.2.2.1 e he).2.2.2.2.2.2.1 + +/-- Checked prefixes meet only at shared graph endpoints. -/ +theorem prefix_routes_contact {g : LocalGeometry} (h : PrefixCheck g) + {e f : LocalIncidence} (he : e ∈ g.incidenceRoutes) (hf : f ∈ g.incidenceRoutes) + (hne : e ≠ f) {p : ℝ × ℝ} (hp : p ∈ support e.2.2) (hq : p ∈ support f.2.2) : + ∃ q ∈ g.commonEndpoints e f, p = realPoint q := + segmentRouteContactCheck_sound (prefix_incidence_length h he) + (prefix_incidence_length h hf) (h.2.2.2.1 e he f hf hne) hp hq + +/-- A checked prefix avoids every nonincident variable vertex. -/ +theorem prefix_variable_not_mem_support {g : LocalGeometry} (h : PrefixCheck g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) + {v : Fin g.variablePositions.length} (hv : v.val ≠ e.2.1) : + realPoint (g.variablePosition v) ∉ support e.2.2 := + not_mem_support_of_routeContactCheck (h.2.2.2.2.1 e he v hv) + +/-- A checked prefix avoids every nonincident clause vertex. -/ +theorem prefix_clause_not_mem_support {g : LocalGeometry} (h : PrefixCheck g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) + {i : Fin g.clausePositions.length} (hi : i.val ≠ e.1) : + realPoint (g.clausePosition i) ∉ support e.2.2 := + not_mem_support_of_routeContactCheck (h.2.2.2.2.2.1 e he i hi) + +/-- The variable endpoint is the only permitted contact with the full cell boundary. -/ +theorem prefix_boundary_contact {g : LocalGeometry} (h : PrefixCheck g) + {e : LocalIncidence} (he : e ∈ g.incidenceRoutes) + {p : ℝ × ℝ} (hp : p ∈ support e.2.2) (hq : p ∈ support g.boundaryRoute) : + p = realPoint (g.variablePosition e.2.1) := by + obtain ⟨q, hq, hpq⟩ := routeContactCheck_sound (h.2.2.2.2.2.2 e he) hp hq + exact List.mem_singleton.mp hq ▸ hpq + +end LocalGeometry + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfiles.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfiles.lean new file mode 100644 index 000000000..43449e42c --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfiles.lean @@ -0,0 +1,80 @@ +/- +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.ExactOne.GeometryProfilesData +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.Geometry +import Mathlib.Tactic.FinCases + +/-! +# Certificates for the actual positive exact-one replacement profiles + +Each finite profile is checked against its full positive exact-one formula, rather than an +unrelated graph. Every source placeholder has just one internal incidence, so it can be removed +when that incidence is joined to the source drawing. Full continuous certificates for the actual +extended incidence routes are in `GeometryProfilesPrefix`. + +## Main results + +The continuous source-boundary certificates are in `GeometryProfilesPrefix`. +* `profileGeometry_signature`: incidence routes match the actual composed positive compiler. +* `profileGeometry_terminals`: exposed variables are exactly the source placeholders. + +## References + +The logical gadgets are from [HuntMaratheRadhakrishnanStearns1998, Theorem 3.8]. +The composed geometric certificates are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- The exact clause-variable incidence sequence of the positive compiled formula. -/ +def profileSignature (p : ClauseProfile) : List (ℕ × ℕ) := + (profileFormula p).zipIdx.flatMap fun (c, i) => + c.map fun l => (i, (profileVariables p).idxOf l.1) + +/-- Geometry and logical variable lists have exactly corresponding positions. -/ +theorem profileGeometry_variable_length (p : ClauseProfile) : + (profileGeometry p).variablePositions.length = (profileVariables p).length := by + fin_cases p <;> decide +kernel + +/-- There is one clause position for each actual emitted positive clause. -/ +theorem profileGeometry_clause_length (p : ClauseProfile) : + (profileGeometry p).clausePositions.length = (profileFormula p).length := by + fin_cases p <;> decide +kernel + +/-- Every incidence appears once, in actual output-clause and literal-position order. -/ +theorem profileGeometry_signature (p : ClauseProfile) : + ((profileGeometry p).incidenceRoutes.map fun e => (e.1, e.2.1)) = profileSignature p := by + fin_cases p <;> decide +kernel + +/-- Exposed variables are precisely the source slots, in source literal order. -/ +theorem profileGeometry_terminals (p : ClauseProfile) : + ((profileGeometry p).terminalVariables.map fun v => + (profileVariables p).getD v (.inl (.inl 0))) = + (List.range (profileSource p).length).map (fun v => Sum.inl (Sum.inl v)) := by + fin_cases p <;> decide +kernel + +/-- Each source placeholder has a single internal incidence, ready for exterior splicing. -/ +theorem profileGeometry_terminal_degree (p : ClauseProfile) : + ∀ v ∈ (profileGeometry p).terminalVariables, + ((profileGeometry p).incidenceRoutes.map fun e => e.2.1).count v = 1 := by + fin_cases p <;> decide +kernel + +/-- The actual composed gadgets have at most twenty-two clauses and sixty-six incidences. -/ +theorem profileGeometry_costs (p : ClauseProfile) : + (profileGeometry p).clausePositions.length ≤ 22 ∧ + (profileGeometry p).incidenceRoutes.length ≤ 66 ∧ + (profileGeometry p).size = 180000 := by + fin_cases p <;> decide +kernel + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesCollar.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesCollar.lean new file mode 100644 index 000000000..0806689bb --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesCollar.lean @@ -0,0 +1,362 @@ +/- +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.ExactOne.GeometryProfilesPrefixData +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfiles +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryCollarGlue + +/-! +# Collared positive exact-one profiles + +The twenty-four ordered injections into the four exterior directions are handled by the common +annular collars. All geometry proofs reuse the fifteen inner-profile certificates and the +twenty-four collar certificates; they do not repeat an exhaustive route-pair audit for every +combination. + +## Main results + +* `collaredProfile_incidence_orthogonal`, `collaredProfile_incidence_isSimple`: actual placed + incidence routes remain proper and continuously simple. + +## References + +These are original continuous gluing certificates for the exact-one replacement profiles. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Orthogonal + +/-- Every source endpoint of the actual inner prefix is its indexed north, west or east port. -/ +theorem profile_prefix_terminal_endpoint (p : ClauseProfile) : + ∀ v ∈ (profilePrefixGeometry p).terminalVariables, + (profilePrefixGeometry p).variablePosition v = + innerTemplatePort (terminalIndex (profilePrefixGeometry p) v) := by + fin_cases p <;> decide +kernel + +/-- Distinct exposed source variables have distinct collar indices. -/ +theorem profile_prefix_terminalIndex_injective (p : ClauseProfile) : + ∀ v ∈ (profilePrefixGeometry p).terminalVariables, + ∀ w ∈ (profilePrefixGeometry p).terminalVariables, + terminalIndex (profilePrefixGeometry p) v = + terminalIndex (profilePrefixGeometry p) w → v = w := by + fin_cases p <;> decide +kernel + +/-- Every source placeholder has a valid local variable index. -/ +theorem profile_prefix_terminal_lt (p : ClauseProfile) : + ∀ v ∈ (profilePrefixGeometry p).terminalVariables, + v < (profilePrefixGeometry p).variablePositions.length := by + fin_cases p <;> decide +kernel + +/-- Every listed variable of the inner template lies in its closed square. -/ +theorem profile_prefix_variable_bounds (p : ClauseProfile) : + ∀ v : Fin (profilePrefixGeometry p).variablePositions.length, + let q := (profilePrefixGeometry p).variablePosition v + 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000 := by + fin_cases p <;> decide +kernel + +/-- Every auxiliary variable lies strictly inside the inner square. -/ +theorem profile_prefix_auxiliary_interior (p : ClauseProfile) : + ∀ v : Fin (profilePrefixGeometry p).variablePositions.length, + v.val ∉ (profilePrefixGeometry p).terminalVariables → + let q := (profilePrefixGeometry p).variablePosition v + 0 < q.1 ∧ q.1 < 180000 ∧ 0 < q.2 ∧ q.2 < 180000 := by + fin_cases p <;> decide +kernel + +/-- An auxiliary variable never occupies a source boundary port. -/ +theorem profile_prefix_auxiliary_ne_terminal (p : ClauseProfile) : + ∀ v : Fin (profilePrefixGeometry p).variablePositions.length, + v.val ∉ (profilePrefixGeometry p).terminalVariables → + ∀ w ∈ (profilePrefixGeometry p).terminalVariables, + (profilePrefixGeometry p).variablePosition v ≠ + (profilePrefixGeometry p).variablePosition w := by + fin_cases p <;> decide +kernel + +/-- Every listed clause position lies in the inner template's closed square. -/ +theorem profile_prefix_clause_bounds (p : ClauseProfile) : + ∀ i : Fin (profilePrefixGeometry p).clausePositions.length, + let q := (profilePrefixGeometry p).clausePosition i + 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000 := by + fin_cases p <;> decide +kernel + +/-- Every actual clause vertex lies strictly inside the inner square. -/ +theorem profile_prefix_clause_interior (p : ClauseProfile) : + ∀ q ∈ (profilePrefixGeometry p).clausePositions, + 0 < q.1 ∧ q.1 < 180000 ∧ 0 < q.2 ∧ q.2 < 180000 := by + fin_cases p <;> decide +kernel + +/-- All listed incidence points lie in the inner square, independently of the expensive +continuous pair-contact certificate. -/ +theorem profile_prefix_incidence_points_bounds (p : ClauseProfile) : + ∀ e ∈ (profilePrefixGeometry p).incidenceRoutes, + ∀ q ∈ e.2.2, 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000 := by + fin_cases p <;> decide +kernel + +/-- No clause vertex occupies a source boundary port. -/ +theorem profile_prefix_clause_ne_terminal (p : ClauseProfile) : + ∀ i : Fin (profilePrefixGeometry p).clausePositions.length, + ∀ w ∈ (profilePrefixGeometry p).terminalVariables, + (profilePrefixGeometry p).clausePosition i ≠ + (profilePrefixGeometry p).variablePosition w := by + fin_cases p <;> decide +kernel + +/-- Each source placeholder belongs to exactly one local incidence. -/ +theorem profile_prefix_terminal_unique (p : ClauseProfile) : + ∀ e ∈ (profilePrefixGeometry p).incidenceRoutes, + e.2.1 ∈ (profilePrefixGeometry p).terminalVariables → + ∀ f ∈ (profilePrefixGeometry p).incidenceRoutes, e.2.1 = f.2.1 → e = f := by + fin_cases p <;> decide +kernel + +/-- Two different incidences can share only an actual auxiliary variable, never a source +placeholder that will be removed by the global drawing compiler. -/ +theorem profile_prefix_shared_variable_not_terminal (p : ClauseProfile) + {e f : LocalIncidence} (he : e ∈ (profilePrefixGeometry p).incidenceRoutes) + (hf : f ∈ (profilePrefixGeometry p).incidenceRoutes) (hne : e ≠ f) + (hv : e.2.1 = f.2.1) : e.2.1 ∉ (profilePrefixGeometry p).terminalVariables := by + intro ht + exact hne (profile_prefix_terminal_unique p e he ht f hf hv) + +/-- The finite route actually used by the collared incidence constructor. -/ +def profileCollarRoute (p : ClauseProfile) (s : PortAssignment) (e : LocalIncidence) : Route := + if e.2.1 ∈ (profilePrefixGeometry p).terminalVariables then + collarPrefixRoute s (terminalIndex (profilePrefixGeometry p) e.2.1) e.2.2 + else collarRoute (collarReflected s) e.2.2 + +/-- Every actual inner prefix lies in the common side-180000 square. -/ +theorem profile_prefix_incidence_bounds (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {e : LocalIncidence} (he : e ∈ (profilePrefixGeometry p).incidenceRoutes) : + ∀ q ∈ e.2.2, 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000 := by + have hsize : (profilePrefixGeometry p).size = 180000 := + (profileGeometry_costs p).2.2 + simpa only [hsize, Nat.cast_ofNat] using LocalGeometry.prefix_incidence_bounds h he + +/-- The actual collared incidence routes are properly orthogonal in every ordered port +assignment. The profile certificate is supplied separately to keep the data compiler lightweight. +-/ +theorem collaredProfile_incidence_orthogonal (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e : LocalIncidence} + (he : e ∈ (collaredPrefixGeometry (profilePrefixGeometry p) s).incidenceRoutes) : + IsOrthogonal e.2.2 := by + obtain ⟨f, hf, rfl⟩ := List.mem_map.mp he + dsimp only + split_ifs with ht + · have hend := (LocalGeometry.prefix_incidence_endpoints h hf).2.2.2 + rw [profile_prefix_terminal_endpoint p f.2.1 ht] at hend + exact isOrthogonal_collarPrefixRoute hs hend + (LocalGeometry.prefix_incidence_orthogonal h hf) + · exact isOrthogonal_collarRoute _ (LocalGeometry.prefix_incidence_orthogonal h hf) + +/-- The actual collared incidence routes are simple over their full real support. -/ +theorem collaredProfile_incidence_isSimple (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e : LocalIncidence} + (he : e ∈ (collaredPrefixGeometry (profilePrefixGeometry p) s).incidenceRoutes) : + IsSimple e.2.2 := by + obtain ⟨f, hf, rfl⟩ := List.mem_map.mp he + dsimp only + split_ifs with ht + · have hend := (LocalGeometry.prefix_incidence_endpoints h hf).2.2.2 + rw [profile_prefix_terminal_endpoint p f.2.1 ht] at hend + exact isSimple_collarPrefixRoute hs hend (LocalGeometry.prefix_incidence_isSimple h hf) + (profile_prefix_incidence_bounds p h hf) + · exact isSimple_collarRoute _ (LocalGeometry.prefix_incidence_isSimple h hf) + +private theorem profile_collar_core_contact (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} {e f : LocalIncidence} + (he : e ∈ (profilePrefixGeometry p).incidenceRoutes) + (hf : f ∈ (profilePrefixGeometry p).incidenceRoutes) (hne : e ≠ f) + {z : ℝ × ℝ} (hz : z ∈ support (collarRoute (collarReflected s) e.2.2)) + (hw : z ∈ support (collarRoute (collarReflected s) f.2.2)) : + ∃ q ∈ (profilePrefixGeometry p).commonEndpoints e f, + z = collarReal (collarReflected s) (realPoint q) := by + rw [support_collarRoute] at hz hw + obtain ⟨x, hx, hxz⟩ := hz + obtain ⟨y, hy, hyz⟩ := hw + have hxy := collarReal_injective (collarReflected s) (hxz.trans hyz.symm) + subst y + obtain ⟨q, hq, hxq⟩ := LocalGeometry.prefix_routes_contact h he hf hne hx hy + exact ⟨q, hq, hxz.symm.trans (congrArg (collarReal (collarReflected s)) hxq)⟩ + +private theorem profile_collar_connector_disjoint (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e f : LocalIncidence} + (he : e ∈ (profilePrefixGeometry p).incidenceRoutes) + (hf : f ∈ (profilePrefixGeometry p).incidenceRoutes) + (ht : f.2.1 ∈ (profilePrefixGeometry p).terminalVariables) (hv : f.2.1 ≠ e.2.1) : + Disjoint (support (collarRoute (collarReflected s) e.2.2)) + (support ((collarRoutes s).getD (terminalIndex (profilePrefixGeometry p) f.2.1) [])) := by + apply disjoint_collarRoute_connector hs (profile_prefix_incidence_bounds p h he) + rw [← profile_prefix_terminal_endpoint p f.2.1 ht] + exact LocalGeometry.prefix_variable_not_mem_support h he + (v := ⟨f.2.1, (LocalGeometry.prefix_incidence_endpoints h hf).2.1⟩) hv + +/-- The annular connectors introduce no new contacts between different incidence routes. +Every contact is the common affine placement of an original permitted local endpoint. -/ +theorem profileCollarRoute_contact (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e f : LocalIncidence} + (he : e ∈ (profilePrefixGeometry p).incidenceRoutes) + (hf : f ∈ (profilePrefixGeometry p).incidenceRoutes) (hne : e ≠ f) + {z : ℝ × ℝ} (hz : z ∈ support (profileCollarRoute p s e)) + (hw : z ∈ support (profileCollarRoute p s f)) : + ∃ q ∈ (profilePrefixGeometry p).commonEndpoints e f, + z = collarReal (collarReflected s) (realPoint q) := by + have hve (ht : e.2.1 ∈ (profilePrefixGeometry p).terminalVariables) : e.2.1 ≠ f.2.1 := by + intro hv + exact profile_prefix_shared_variable_not_terminal p he hf hne hv ht + have hvf (ht : f.2.1 ∈ (profilePrefixGeometry p).terminalVariables) : f.2.1 ≠ e.2.1 := by + intro hv + exact profile_prefix_shared_variable_not_terminal p hf he (Ne.symm hne) hv ht + unfold profileCollarRoute at hz hw + split_ifs at hz with hte <;> split_ifs at hw with htf + · have hle := (LocalGeometry.prefix_incidence_endpoints h he).2.2.2 + have hlf := (LocalGeometry.prefix_incidence_endpoints h hf).2.2.2 + rw [profile_prefix_terminal_endpoint p e.2.1 hte] at hle + rw [profile_prefix_terminal_endpoint p f.2.1 htf] at hlf + have hidx : terminalIndex (profilePrefixGeometry p) e.2.1 ≠ + terminalIndex (profilePrefixGeometry p) f.2.1 := by + intro heq + exact hve hte (profile_prefix_terminalIndex_injective p e.2.1 hte f.2.1 htf heq) + obtain ⟨q, hqe, hqf, hzq⟩ := collarPrefixRoute_contact hs hidx hle hlf + (profile_prefix_incidence_bounds p h he) (profile_prefix_incidence_bounds p h hf) + (by + rw [← profile_prefix_terminal_endpoint p f.2.1 htf] + exact LocalGeometry.prefix_variable_not_mem_support h he + (v := ⟨f.2.1, (LocalGeometry.prefix_incidence_endpoints h hf).2.1⟩) (hvf htf)) + (by + rw [← profile_prefix_terminal_endpoint p e.2.1 hte] + exact LocalGeometry.prefix_variable_not_mem_support h hf + (v := ⟨e.2.1, (LocalGeometry.prefix_incidence_endpoints h he).2.1⟩) (hve hte)) hz hw + obtain ⟨a, ha, hqa⟩ := LocalGeometry.prefix_routes_contact h he hf hne hqe hqf + exact ⟨a, ha, hzq.trans (congrArg (collarReal (collarReflected s)) hqa)⟩ + · have hle := (LocalGeometry.prefix_incidence_endpoints h he).2.2.2 + rw [profile_prefix_terminal_endpoint p e.2.1 hte] at hle + rw [support_collarPrefixRoute hs hle] at hz + rcases hz with hz | hz + · exact profile_collar_core_contact p h he hf hne hz hw + · exact False.elim (Set.disjoint_left.mp + (profile_collar_connector_disjoint p h hs hf he hte (hve hte)) hw hz) + · have hlf := (LocalGeometry.prefix_incidence_endpoints h hf).2.2.2 + rw [profile_prefix_terminal_endpoint p f.2.1 htf] at hlf + rw [support_collarPrefixRoute hs hlf] at hw + rcases hw with hw | hw + · exact profile_collar_core_contact p h he hf hne hz hw + · exact False.elim (Set.disjoint_left.mp + (profile_collar_connector_disjoint p h hs he hf htf (hvf htf)) hz hw) + · exact profile_collar_core_contact p h he hf hne hz hw + +/-- A collared incidence avoids every nonincident actual auxiliary vertex. -/ +theorem profileCollarRoute_auxiliary_not_mem_support (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e : LocalIncidence} + (he : e ∈ (profilePrefixGeometry p).incidenceRoutes) + (v : Fin (profilePrefixGeometry p).variablePositions.length) + (ht : v.val ∉ (profilePrefixGeometry p).terminalVariables) (hv : v.val ≠ e.2.1) : + realPoint (collarPoint (collarReflected s) ((profilePrefixGeometry p).variablePosition v)) ∉ + support (profileCollarRoute p s e) := by + have hn := LocalGeometry.prefix_variable_not_mem_support h he (v := v) hv + unfold profileCollarRoute + split_ifs with hsource + · have hend := (LocalGeometry.prefix_incidence_endpoints h he).2.2.2 + rw [profile_prefix_terminal_endpoint p e.2.1 hsource] at hend + apply collarPrefixRoute_not_mem_corePoint hs hend (profile_prefix_variable_bounds p v) + ?_ hn + rw [← profile_prefix_terminal_endpoint p e.2.1 hsource] + exact profile_prefix_auxiliary_ne_terminal p v ht e.2.1 hsource + · rw [realPoint_collarPoint] + exact fun hp => hn ((mem_support_collarRoute_iff _ _ _).mp hp) + +/-- A collared incidence avoids every nonincident actual clause vertex. -/ +theorem profileCollarRoute_clause_not_mem_support (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e : LocalIncidence} + (he : e ∈ (profilePrefixGeometry p).incidenceRoutes) + (i : Fin (profilePrefixGeometry p).clausePositions.length) (hi : i.val ≠ e.1) : + realPoint (collarPoint (collarReflected s) ((profilePrefixGeometry p).clausePosition i)) ∉ + support (profileCollarRoute p s e) := by + have hn := LocalGeometry.prefix_clause_not_mem_support h he (i := i) hi + unfold profileCollarRoute + split_ifs with hsource + · have hend := (LocalGeometry.prefix_incidence_endpoints h he).2.2.2 + rw [profile_prefix_terminal_endpoint p e.2.1 hsource] at hend + apply collarPrefixRoute_not_mem_corePoint hs hend (profile_prefix_clause_bounds p i) ?_ hn + rw [← profile_prefix_terminal_endpoint p e.2.1 hsource] + exact profile_prefix_clause_ne_terminal p i e.2.1 hsource + · rw [realPoint_collarPoint] + exact fun hp => hn ((mem_support_collarRoute_iff _ _ _).mp hp) + +/-- A collared incidence avoids every other exposed source endpoint. -/ +theorem profileCollarRoute_source_not_mem_support (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e : LocalIncidence} + (he : e ∈ (profilePrefixGeometry p).incidenceRoutes) {v : ℕ} + (ht : v ∈ (profilePrefixGeometry p).terminalVariables) (hv : v ≠ e.2.1) : + realPoint (collarOuterPort (s.side (terminalIndex (profilePrefixGeometry p) v))) ∉ + support (profileCollarRoute p s e) := by + let i := terminalIndex (profilePrefixGeometry p) v + have hhead := ((collar_check s hs).2 i).1 + have hm : collarOuterPort (s.side i) ∈ (collarRoutes s).getD i [] := by + cases hr : (collarRoutes s).getD i [] with + | nil => rw [hr] at hhead; cases hhead + | cons a as => + have ha : a = collarOuterPort (s.side i) := by + rw [hr] at hhead + exact Option.some.inj hhead + rw [← ha] + exact List.mem_cons_self + have hsupp : realPoint (collarOuterPort (s.side i)) ∈ + support ((collarRoutes s).getD i []) := Or.inl ⟨_, hm, rfl⟩ + have hd : Disjoint (support (collarRoute (collarReflected s) e.2.2)) + (support ((collarRoutes s).getD i [])) := by + apply disjoint_collarRoute_connector hs (profile_prefix_incidence_bounds p h he) + rw [← profile_prefix_terminal_endpoint p v ht] + exact LocalGeometry.prefix_variable_not_mem_support h he + (v := ⟨v, profile_prefix_terminal_lt p v ht⟩) hv + intro hp + unfold profileCollarRoute at hp + split_ifs at hp with heterm + · have hend := (LocalGeometry.prefix_incidence_endpoints h he).2.2.2 + rw [profile_prefix_terminal_endpoint p e.2.1 heterm] at hend + rw [support_collarPrefixRoute hs hend] at hp + rcases hp with hp | hp + · exact Set.disjoint_left.mp hd hp hsupp + · have hi : terminalIndex (profilePrefixGeometry p) e.2.1 ≠ i := by + intro hi + exact hv (profile_prefix_terminalIndex_injective p e.2.1 heterm v ht hi).symm + exact Set.disjoint_left.mp (collar_disjoint hs hi) hp hsupp + · exact Set.disjoint_left.mp hd hp hsupp + +/-- A collared route can touch the complete outer perimeter only at its own source endpoint; +an incidence to an internal auxiliary never touches that perimeter. -/ +theorem profileCollarRoute_outer_boundary_contact (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e : LocalIncidence} + (he : e ∈ (profilePrefixGeometry p).incidenceRoutes) + {z : ℝ × ℝ} (hz : z ∈ support (profileCollarRoute p s e)) + (hb : z ∈ support collarBoundaryRoute) : + e.2.1 ∈ (profilePrefixGeometry p).terminalVariables ∧ + z = realPoint (collarOuterPort (s.side (terminalIndex (profilePrefixGeometry p) e.2.1))) := by + unfold profileCollarRoute at hz + split_ifs at hz with ht + · have hend := (LocalGeometry.prefix_incidence_endpoints h he).2.2.2 + rw [profile_prefix_terminal_endpoint p e.2.1 ht] at hend + exact ⟨ht, collarPrefixRoute_outer_boundary_contact hs hend + (profile_prefix_incidence_bounds p h he) hz hb⟩ + · have hcheck : routeBoxContactCheck [] collarBoundaryRoute (270000, 270000) 90000 = + true := by decide +kernel + obtain ⟨q, hq, _⟩ := routeBoxContactCheck_sound hcheck hb + (collarRoute_in_innerBox _ (profile_prefix_incidence_bounds p h he) hz) + exact False.elim (List.not_mem_nil hq) + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesData.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesData.lean new file mode 100644 index 000000000..917a62a59 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesData.lean @@ -0,0 +1,1373 @@ +/- +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.ExactOne.GeometryData +public import Complexity.Logics.Propositional.CNF.ExactOne.Compile + +/-! +# Composed positive exact-one geometry profiles + +The fifteen profiles describe zero through three source literal positions with every polarity +pattern. The coordinates compose the parsimonious disjunction, positive complement and forced +false gadgets. Every table matches the actual positive clause compiler, including its clause +order, variable names and auxiliary occurrences. Source terminals have one internal incidence; +the global drawing compiler removes their placeholder positions and joins their accesses to +the inherited source incidence routes. + +## Main definitions + +* `profileSource`, `profileFormula`, `profileVariables`: the exact logical profile. +* `profileGeometry`: its fixed orthogonal replacement data. + +## References + +The logical gadgets are from [HuntMaratheRadhakrishnanStearns1998, Theorem 3.8]. +The coordinates and finite profiles are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- Fifteen signed source clauses: every sign pattern on zero through three distinct slots. -/ +abbrev ClauseProfile := Fin 15 + +/-- The signed source clause of a fixed profile, with variables named by their positions. -/ +def profileSource (p : ClauseProfile) : Clause ℕ := + match p.val with + | 0 => [] + | 1 => [(0, false)] + | 2 => [(0, true)] + | 3 => [(0, false), (1, false)] + | 4 => [(0, false), (1, true)] + | 5 => [(0, true), (1, false)] + | 6 => [(0, true), (1, true)] + | 7 => [(0, false), (1, false), (2, false)] + | 8 => [(0, false), (1, false), (2, true)] + | 9 => [(0, false), (1, true), (2, false)] + | 10 => [(0, false), (1, true), (2, true)] + | 11 => [(0, true), (1, false), (2, false)] + | 12 => [(0, true), (1, false), (2, true)] + | 13 => [(0, true), (1, true), (2, false)] + | 14 => [(0, true), (1, true), (2, true)] + | _ => [] + +/-- The actual composed logical compiler on one source profile. -/ +def profileFormula (p : ClauseProfile) : CNF (CNF.PositiveExactOneCompile.Variable ℕ) := + CNF.PositiveExactOne.formula (CNF.FromSAT.clause 0 (profileSource p)) + +/-- The distinct logical variables, in the same order as the profile's variable positions. -/ +def profileVariables (p : ClauseProfile) : List (CNF.PositiveExactOneCompile.Variable ℕ) := + (profileFormula p).distinctVars + +/-- Fixed profile 0: source polarities `[]`. -/ +def profileGeometry0 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (20000, 100000), + (70000, 100000), + (18800, 100000), + (19400, 100400), + (18800, 101200), + (19400, 100800), + (20000, 101200), + (100000, 120000), + (140000, 20000), + (160000, 40000), + (141200, 20000), + (140600, 19600), + (141200, 18800), + (140600, 19200), + (140000, 18800), + (20060, 19920), + (20000, 19960), + (19940, 19920), + (18720, 99940), + (18760, 100000), + (18720, 100060), + (141280, 20060), + (141240, 20000), + (141280, 19940)] + clausePositions := + [(40000, 40000), + (40000, 100000), + (19400, 100000), + (19000, 100600), + (19800, 100600), + (19400, 101400), + (100000, 100000), + (140000, 40000), + (140600, 20000), + (141000, 19400), + (140200, 19400), + (140600, 18600), + (20040, 19980), + (19960, 19980), + (20000, 19900), + (18780, 99960), + (18780, 100040), + (18700, 100000), + (141220, 20040), + (141220, 19960), + (141300, 20000)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(40000, 100000), (20000, 100000)]), + (1, 1, [(40000, 100000), (40000, 60000)]), + (1, 4, [(40000, 100000), (70000, 100000)]), + (2, 5, [(19400, 100000), (18800, 100000)]), + (2, 3, [(19400, 100000), (20000, 100000)]), + (2, 6, [(19400, 100000), (19400, 100400)]), + (3, 6, [(19000, 100600), (19000, 100400), (19400, 100400)]), + (3, 7, [(19000, 100600), (18800, 100600), (18800, 101200)]), + (3, 8, [(19000, 100600), (19000, 100800), (19400, 100800)]), + (4, 6, [(19800, 100600), (19800, 100400), (19400, 100400)]), + (4, 8, [(19800, 100600), (19800, 100800), (19400, 100800)]), + (4, 9, [(19800, 100600), (20000, 100600), (20000, 101200)]), + (5, 7, [(19400, 101400), (18800, 101400), (18800, 101200)]), + (5, 8, [(19400, 101400), (19400, 100800)]), + (5, 9, [(19400, 101400), (20000, 101400), (20000, 101200)]), + (6, 2, [(100000, 100000), (100000, 40000)]), + (6, 4, [(100000, 100000), (70000, 100000)]), + (6, 10, [(100000, 100000), (100000, 120000)]), + (7, 11, [(140000, 40000), (140000, 20000)]), + (7, 2, [(140000, 40000), (100000, 40000)]), + (7, 12, [(140000, 40000), (160000, 40000)]), + (8, 13, [(140600, 20000), (141200, 20000)]), + (8, 11, [(140600, 20000), (140000, 20000)]), + (8, 14, [(140600, 20000), (140600, 19600)]), + (9, 14, [(141000, 19400), (141000, 19600), (140600, 19600)]), + (9, 15, [(141000, 19400), (141200, 19400), (141200, 18800)]), + (9, 16, [(141000, 19400), (141000, 19200), (140600, 19200)]), + (10, 14, [(140200, 19400), (140200, 19600), (140600, 19600)]), + (10, 16, [(140200, 19400), (140200, 19200), (140600, 19200)]), + (10, 17, [(140200, 19400), (140000, 19400), (140000, 18800)]), + (11, 15, [(140600, 18600), (141200, 18600), (141200, 18800)]), + (11, 16, [(140600, 18600), (140600, 19200)]), + (11, 17, [(140600, 18600), (140000, 18600), (140000, 18800)]), + (12, 0, [(20040, 19980), (20040, 20000), (20000, 20000)]), + (12, 18, [(20040, 19980), (20060, 19980), (20060, 19920)]), + (12, 19, [(20040, 19980), (20040, 19960), (20000, 19960)]), + (13, 0, [(19960, 19980), (19960, 20000), (20000, 20000)]), + (13, 19, [(19960, 19980), (19960, 19960), (20000, 19960)]), + (13, 20, [(19960, 19980), (19940, 19980), (19940, 19920)]), + (14, 18, [(20000, 19900), (20060, 19900), (20060, 19920)]), + (14, 19, [(20000, 19900), (20000, 19960)]), + (14, 20, [(20000, 19900), (19940, 19900), (19940, 19920)]), + (15, 5, [(18780, 99960), (18800, 99960), (18800, 100000)]), + (15, 21, [(18780, 99960), (18780, 99940), (18720, 99940)]), + (15, 22, [(18780, 99960), (18760, 99960), (18760, 100000)]), + (16, 5, [(18780, 100040), (18800, 100040), (18800, 100000)]), + (16, 22, [(18780, 100040), (18760, 100040), (18760, 100000)]), + (16, 23, [(18780, 100040), (18780, 100060), (18720, 100060)]), + (17, 21, [(18700, 100000), (18700, 99940), (18720, 99940)]), + (17, 22, [(18700, 100000), (18760, 100000)]), + (17, 23, [(18700, 100000), (18700, 100060), (18720, 100060)]), + (18, 13, [(141220, 20040), (141200, 20040), (141200, 20000)]), + (18, 24, [(141220, 20040), (141220, 20060), (141280, 20060)]), + (18, 25, [(141220, 20040), (141240, 20040), (141240, 20000)]), + (19, 13, [(141220, 19960), (141200, 19960), (141200, 20000)]), + (19, 25, [(141220, 19960), (141240, 19960), (141240, 20000)]), + (19, 26, [(141220, 19960), (141220, 19940), (141280, 19940)]), + (20, 24, [(141300, 20000), (141300, 20060), (141280, 20060)]), + (20, 25, [(141300, 20000), (141240, 20000)]), + (20, 26, [(141300, 20000), (141300, 19940), (141280, 19940)])] + terminalVariables := [] + boundaryPorts := [] + accessRoutes := + [] + +/-- Fixed profile 1: source polarities `[false]`. -/ +def profileGeometry1 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (21200, 20000), + (20600, 19600), + (21200, 18800), + (20600, 19200), + (20000, 18800), + (20000, 100000), + (70000, 100000), + (18800, 100000), + (19400, 100400), + (18800, 101200), + (19400, 100800), + (20000, 101200), + (100000, 120000), + (140000, 20000), + (160000, 40000), + (141200, 20000), + (140600, 19600), + (141200, 18800), + (140600, 19200), + (140000, 18800), + (18720, 99940), + (18760, 100000), + (18720, 100060), + (141280, 20060), + (141240, 20000), + (141280, 19940)] + clausePositions := + [(40000, 40000), + (20600, 20000), + (21000, 19400), + (20200, 19400), + (20600, 18600), + (40000, 100000), + (19400, 100000), + (19000, 100600), + (19800, 100600), + (19400, 101400), + (100000, 100000), + (140000, 40000), + (140600, 20000), + (141000, 19400), + (140200, 19400), + (140600, 18600), + (18780, 99960), + (18780, 100040), + (18700, 100000), + (141220, 20040), + (141220, 19960), + (141300, 20000)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(20600, 20000), (21200, 20000)]), + (1, 0, [(20600, 20000), (20000, 20000)]), + (1, 4, [(20600, 20000), (20600, 19600)]), + (2, 4, [(21000, 19400), (21000, 19600), (20600, 19600)]), + (2, 5, [(21000, 19400), (21200, 19400), (21200, 18800)]), + (2, 6, [(21000, 19400), (21000, 19200), (20600, 19200)]), + (3, 4, [(20200, 19400), (20200, 19600), (20600, 19600)]), + (3, 6, [(20200, 19400), (20200, 19200), (20600, 19200)]), + (3, 7, [(20200, 19400), (20000, 19400), (20000, 18800)]), + (4, 5, [(20600, 18600), (21200, 18600), (21200, 18800)]), + (4, 6, [(20600, 18600), (20600, 19200)]), + (4, 7, [(20600, 18600), (20000, 18600), (20000, 18800)]), + (5, 8, [(40000, 100000), (20000, 100000)]), + (5, 1, [(40000, 100000), (40000, 60000)]), + (5, 9, [(40000, 100000), (70000, 100000)]), + (6, 10, [(19400, 100000), (18800, 100000)]), + (6, 8, [(19400, 100000), (20000, 100000)]), + (6, 11, [(19400, 100000), (19400, 100400)]), + (7, 11, [(19000, 100600), (19000, 100400), (19400, 100400)]), + (7, 12, [(19000, 100600), (18800, 100600), (18800, 101200)]), + (7, 13, [(19000, 100600), (19000, 100800), (19400, 100800)]), + (8, 11, [(19800, 100600), (19800, 100400), (19400, 100400)]), + (8, 13, [(19800, 100600), (19800, 100800), (19400, 100800)]), + (8, 14, [(19800, 100600), (20000, 100600), (20000, 101200)]), + (9, 12, [(19400, 101400), (18800, 101400), (18800, 101200)]), + (9, 13, [(19400, 101400), (19400, 100800)]), + (9, 14, [(19400, 101400), (20000, 101400), (20000, 101200)]), + (10, 2, [(100000, 100000), (100000, 40000)]), + (10, 9, [(100000, 100000), (70000, 100000)]), + (10, 15, [(100000, 100000), (100000, 120000)]), + (11, 16, [(140000, 40000), (140000, 20000)]), + (11, 2, [(140000, 40000), (100000, 40000)]), + (11, 17, [(140000, 40000), (160000, 40000)]), + (12, 18, [(140600, 20000), (141200, 20000)]), + (12, 16, [(140600, 20000), (140000, 20000)]), + (12, 19, [(140600, 20000), (140600, 19600)]), + (13, 19, [(141000, 19400), (141000, 19600), (140600, 19600)]), + (13, 20, [(141000, 19400), (141200, 19400), (141200, 18800)]), + (13, 21, [(141000, 19400), (141000, 19200), (140600, 19200)]), + (14, 19, [(140200, 19400), (140200, 19600), (140600, 19600)]), + (14, 21, [(140200, 19400), (140200, 19200), (140600, 19200)]), + (14, 22, [(140200, 19400), (140000, 19400), (140000, 18800)]), + (15, 20, [(140600, 18600), (141200, 18600), (141200, 18800)]), + (15, 21, [(140600, 18600), (140600, 19200)]), + (15, 22, [(140600, 18600), (140000, 18600), (140000, 18800)]), + (16, 10, [(18780, 99960), (18800, 99960), (18800, 100000)]), + (16, 23, [(18780, 99960), (18780, 99940), (18720, 99940)]), + (16, 24, [(18780, 99960), (18760, 99960), (18760, 100000)]), + (17, 10, [(18780, 100040), (18800, 100040), (18800, 100000)]), + (17, 24, [(18780, 100040), (18760, 100040), (18760, 100000)]), + (17, 25, [(18780, 100040), (18780, 100060), (18720, 100060)]), + (18, 23, [(18700, 100000), (18700, 99940), (18720, 99940)]), + (18, 24, [(18700, 100000), (18760, 100000)]), + (18, 25, [(18700, 100000), (18700, 100060), (18720, 100060)]), + (19, 18, [(141220, 20040), (141200, 20040), (141200, 20000)]), + (19, 26, [(141220, 20040), (141220, 20060), (141280, 20060)]), + (19, 27, [(141220, 20040), (141240, 20040), (141240, 20000)]), + (20, 18, [(141220, 19960), (141200, 19960), (141200, 20000)]), + (20, 27, [(141220, 19960), (141240, 19960), (141240, 20000)]), + (20, 28, [(141220, 19960), (141220, 19940), (141280, 19940)]), + (21, 26, [(141300, 20000), (141300, 20060), (141280, 20060)]), + (21, 27, [(141300, 20000), (141240, 20000)]), + (21, 28, [(141300, 20000), (141300, 19940), (141280, 19940)])] + terminalVariables := [3] + boundaryPorts := [(90000, 0)] + accessRoutes := + [[(90000, 0), (90000, 10000), (21500, 10000), (21500, 19300), (21300, 19300), (21300, 20000), + (21200, 20000)]] + +/-- Fixed profile 2: source polarities `[true]`. -/ +def profileGeometry2 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (20000, 100000), + (70000, 100000), + (18800, 100000), + (19400, 100400), + (18800, 101200), + (19400, 100800), + (20000, 101200), + (100000, 120000), + (140000, 20000), + (160000, 40000), + (141200, 20000), + (140600, 19600), + (141200, 18800), + (140600, 19200), + (140000, 18800), + (18720, 99940), + (18760, 100000), + (18720, 100060), + (141280, 20060), + (141240, 20000), + (141280, 19940)] + clausePositions := + [(40000, 40000), + (40000, 100000), + (19400, 100000), + (19000, 100600), + (19800, 100600), + (19400, 101400), + (100000, 100000), + (140000, 40000), + (140600, 20000), + (141000, 19400), + (140200, 19400), + (140600, 18600), + (18780, 99960), + (18780, 100040), + (18700, 100000), + (141220, 20040), + (141220, 19960), + (141300, 20000)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(40000, 100000), (20000, 100000)]), + (1, 1, [(40000, 100000), (40000, 60000)]), + (1, 4, [(40000, 100000), (70000, 100000)]), + (2, 5, [(19400, 100000), (18800, 100000)]), + (2, 3, [(19400, 100000), (20000, 100000)]), + (2, 6, [(19400, 100000), (19400, 100400)]), + (3, 6, [(19000, 100600), (19000, 100400), (19400, 100400)]), + (3, 7, [(19000, 100600), (18800, 100600), (18800, 101200)]), + (3, 8, [(19000, 100600), (19000, 100800), (19400, 100800)]), + (4, 6, [(19800, 100600), (19800, 100400), (19400, 100400)]), + (4, 8, [(19800, 100600), (19800, 100800), (19400, 100800)]), + (4, 9, [(19800, 100600), (20000, 100600), (20000, 101200)]), + (5, 7, [(19400, 101400), (18800, 101400), (18800, 101200)]), + (5, 8, [(19400, 101400), (19400, 100800)]), + (5, 9, [(19400, 101400), (20000, 101400), (20000, 101200)]), + (6, 2, [(100000, 100000), (100000, 40000)]), + (6, 4, [(100000, 100000), (70000, 100000)]), + (6, 10, [(100000, 100000), (100000, 120000)]), + (7, 11, [(140000, 40000), (140000, 20000)]), + (7, 2, [(140000, 40000), (100000, 40000)]), + (7, 12, [(140000, 40000), (160000, 40000)]), + (8, 13, [(140600, 20000), (141200, 20000)]), + (8, 11, [(140600, 20000), (140000, 20000)]), + (8, 14, [(140600, 20000), (140600, 19600)]), + (9, 14, [(141000, 19400), (141000, 19600), (140600, 19600)]), + (9, 15, [(141000, 19400), (141200, 19400), (141200, 18800)]), + (9, 16, [(141000, 19400), (141000, 19200), (140600, 19200)]), + (10, 14, [(140200, 19400), (140200, 19600), (140600, 19600)]), + (10, 16, [(140200, 19400), (140200, 19200), (140600, 19200)]), + (10, 17, [(140200, 19400), (140000, 19400), (140000, 18800)]), + (11, 15, [(140600, 18600), (141200, 18600), (141200, 18800)]), + (11, 16, [(140600, 18600), (140600, 19200)]), + (11, 17, [(140600, 18600), (140000, 18600), (140000, 18800)]), + (12, 5, [(18780, 99960), (18800, 99960), (18800, 100000)]), + (12, 18, [(18780, 99960), (18780, 99940), (18720, 99940)]), + (12, 19, [(18780, 99960), (18760, 99960), (18760, 100000)]), + (13, 5, [(18780, 100040), (18800, 100040), (18800, 100000)]), + (13, 19, [(18780, 100040), (18760, 100040), (18760, 100000)]), + (13, 20, [(18780, 100040), (18780, 100060), (18720, 100060)]), + (14, 18, [(18700, 100000), (18700, 99940), (18720, 99940)]), + (14, 19, [(18700, 100000), (18760, 100000)]), + (14, 20, [(18700, 100000), (18700, 100060), (18720, 100060)]), + (15, 13, [(141220, 20040), (141200, 20040), (141200, 20000)]), + (15, 21, [(141220, 20040), (141220, 20060), (141280, 20060)]), + (15, 22, [(141220, 20040), (141240, 20040), (141240, 20000)]), + (16, 13, [(141220, 19960), (141200, 19960), (141200, 20000)]), + (16, 22, [(141220, 19960), (141240, 19960), (141240, 20000)]), + (16, 23, [(141220, 19960), (141220, 19940), (141280, 19940)]), + (17, 21, [(141300, 20000), (141300, 20060), (141280, 20060)]), + (17, 22, [(141300, 20000), (141240, 20000)]), + (17, 23, [(141300, 20000), (141300, 19940), (141280, 19940)])] + terminalVariables := [0] + boundaryPorts := [(90000, 0)] + accessRoutes := + [[(90000, 0), (90000, 10000), (20000, 10000), (20000, 20000)]] + +/-- Fixed profile 3: source polarities `[false, false]`. -/ +def profileGeometry3 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (21200, 20000), + (20600, 19600), + (21200, 18800), + (20600, 19200), + (20000, 18800), + (20000, 100000), + (70000, 100000), + (100000, 120000), + (140000, 20000), + (160000, 40000), + (141200, 20000), + (140600, 19600), + (141200, 18800), + (140600, 19200), + (140000, 18800), + (141280, 20060), + (141240, 20000), + (141280, 19940)] + clausePositions := + [(40000, 40000), + (20600, 20000), + (21000, 19400), + (20200, 19400), + (20600, 18600), + (40000, 100000), + (100000, 100000), + (140000, 40000), + (140600, 20000), + (141000, 19400), + (140200, 19400), + (140600, 18600), + (141220, 20040), + (141220, 19960), + (141300, 20000)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(20600, 20000), (21200, 20000)]), + (1, 0, [(20600, 20000), (20000, 20000)]), + (1, 4, [(20600, 20000), (20600, 19600)]), + (2, 4, [(21000, 19400), (21000, 19600), (20600, 19600)]), + (2, 5, [(21000, 19400), (21200, 19400), (21200, 18800)]), + (2, 6, [(21000, 19400), (21000, 19200), (20600, 19200)]), + (3, 4, [(20200, 19400), (20200, 19600), (20600, 19600)]), + (3, 6, [(20200, 19400), (20200, 19200), (20600, 19200)]), + (3, 7, [(20200, 19400), (20000, 19400), (20000, 18800)]), + (4, 5, [(20600, 18600), (21200, 18600), (21200, 18800)]), + (4, 6, [(20600, 18600), (20600, 19200)]), + (4, 7, [(20600, 18600), (20000, 18600), (20000, 18800)]), + (5, 8, [(40000, 100000), (20000, 100000)]), + (5, 1, [(40000, 100000), (40000, 60000)]), + (5, 9, [(40000, 100000), (70000, 100000)]), + (6, 2, [(100000, 100000), (100000, 40000)]), + (6, 9, [(100000, 100000), (70000, 100000)]), + (6, 10, [(100000, 100000), (100000, 120000)]), + (7, 11, [(140000, 40000), (140000, 20000)]), + (7, 2, [(140000, 40000), (100000, 40000)]), + (7, 12, [(140000, 40000), (160000, 40000)]), + (8, 13, [(140600, 20000), (141200, 20000)]), + (8, 11, [(140600, 20000), (140000, 20000)]), + (8, 14, [(140600, 20000), (140600, 19600)]), + (9, 14, [(141000, 19400), (141000, 19600), (140600, 19600)]), + (9, 15, [(141000, 19400), (141200, 19400), (141200, 18800)]), + (9, 16, [(141000, 19400), (141000, 19200), (140600, 19200)]), + (10, 14, [(140200, 19400), (140200, 19600), (140600, 19600)]), + (10, 16, [(140200, 19400), (140200, 19200), (140600, 19200)]), + (10, 17, [(140200, 19400), (140000, 19400), (140000, 18800)]), + (11, 15, [(140600, 18600), (141200, 18600), (141200, 18800)]), + (11, 16, [(140600, 18600), (140600, 19200)]), + (11, 17, [(140600, 18600), (140000, 18600), (140000, 18800)]), + (12, 13, [(141220, 20040), (141200, 20040), (141200, 20000)]), + (12, 18, [(141220, 20040), (141220, 20060), (141280, 20060)]), + (12, 19, [(141220, 20040), (141240, 20040), (141240, 20000)]), + (13, 13, [(141220, 19960), (141200, 19960), (141200, 20000)]), + (13, 19, [(141220, 19960), (141240, 19960), (141240, 20000)]), + (13, 20, [(141220, 19960), (141220, 19940), (141280, 19940)]), + (14, 18, [(141300, 20000), (141300, 20060), (141280, 20060)]), + (14, 19, [(141300, 20000), (141240, 20000)]), + (14, 20, [(141300, 20000), (141300, 19940), (141280, 19940)])] + terminalVariables := [3, 8] + boundaryPorts := [(90000, 0), (0, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (21500, 10000), (21500, 19300), (21300, 19300), (21300, 20000), + (21200, 20000)], [(0, 90000), (10000, 90000), (10000, 100000), (20000, 100000)]] + +/-- Fixed profile 4: source polarities `[false, true]`. -/ +def profileGeometry4 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (21200, 20000), + (20600, 19600), + (21200, 18800), + (20600, 19200), + (20000, 18800), + (20000, 100000), + (70000, 100000), + (18800, 100000), + (19400, 100400), + (18800, 101200), + (19400, 100800), + (20000, 101200), + (100000, 120000), + (140000, 20000), + (160000, 40000), + (141200, 20000), + (140600, 19600), + (141200, 18800), + (140600, 19200), + (140000, 18800), + (141280, 20060), + (141240, 20000), + (141280, 19940)] + clausePositions := + [(40000, 40000), + (20600, 20000), + (21000, 19400), + (20200, 19400), + (20600, 18600), + (40000, 100000), + (19400, 100000), + (19000, 100600), + (19800, 100600), + (19400, 101400), + (100000, 100000), + (140000, 40000), + (140600, 20000), + (141000, 19400), + (140200, 19400), + (140600, 18600), + (141220, 20040), + (141220, 19960), + (141300, 20000)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(20600, 20000), (21200, 20000)]), + (1, 0, [(20600, 20000), (20000, 20000)]), + (1, 4, [(20600, 20000), (20600, 19600)]), + (2, 4, [(21000, 19400), (21000, 19600), (20600, 19600)]), + (2, 5, [(21000, 19400), (21200, 19400), (21200, 18800)]), + (2, 6, [(21000, 19400), (21000, 19200), (20600, 19200)]), + (3, 4, [(20200, 19400), (20200, 19600), (20600, 19600)]), + (3, 6, [(20200, 19400), (20200, 19200), (20600, 19200)]), + (3, 7, [(20200, 19400), (20000, 19400), (20000, 18800)]), + (4, 5, [(20600, 18600), (21200, 18600), (21200, 18800)]), + (4, 6, [(20600, 18600), (20600, 19200)]), + (4, 7, [(20600, 18600), (20000, 18600), (20000, 18800)]), + (5, 8, [(40000, 100000), (20000, 100000)]), + (5, 1, [(40000, 100000), (40000, 60000)]), + (5, 9, [(40000, 100000), (70000, 100000)]), + (6, 10, [(19400, 100000), (18800, 100000)]), + (6, 8, [(19400, 100000), (20000, 100000)]), + (6, 11, [(19400, 100000), (19400, 100400)]), + (7, 11, [(19000, 100600), (19000, 100400), (19400, 100400)]), + (7, 12, [(19000, 100600), (18800, 100600), (18800, 101200)]), + (7, 13, [(19000, 100600), (19000, 100800), (19400, 100800)]), + (8, 11, [(19800, 100600), (19800, 100400), (19400, 100400)]), + (8, 13, [(19800, 100600), (19800, 100800), (19400, 100800)]), + (8, 14, [(19800, 100600), (20000, 100600), (20000, 101200)]), + (9, 12, [(19400, 101400), (18800, 101400), (18800, 101200)]), + (9, 13, [(19400, 101400), (19400, 100800)]), + (9, 14, [(19400, 101400), (20000, 101400), (20000, 101200)]), + (10, 2, [(100000, 100000), (100000, 40000)]), + (10, 9, [(100000, 100000), (70000, 100000)]), + (10, 15, [(100000, 100000), (100000, 120000)]), + (11, 16, [(140000, 40000), (140000, 20000)]), + (11, 2, [(140000, 40000), (100000, 40000)]), + (11, 17, [(140000, 40000), (160000, 40000)]), + (12, 18, [(140600, 20000), (141200, 20000)]), + (12, 16, [(140600, 20000), (140000, 20000)]), + (12, 19, [(140600, 20000), (140600, 19600)]), + (13, 19, [(141000, 19400), (141000, 19600), (140600, 19600)]), + (13, 20, [(141000, 19400), (141200, 19400), (141200, 18800)]), + (13, 21, [(141000, 19400), (141000, 19200), (140600, 19200)]), + (14, 19, [(140200, 19400), (140200, 19600), (140600, 19600)]), + (14, 21, [(140200, 19400), (140200, 19200), (140600, 19200)]), + (14, 22, [(140200, 19400), (140000, 19400), (140000, 18800)]), + (15, 20, [(140600, 18600), (141200, 18600), (141200, 18800)]), + (15, 21, [(140600, 18600), (140600, 19200)]), + (15, 22, [(140600, 18600), (140000, 18600), (140000, 18800)]), + (16, 18, [(141220, 20040), (141200, 20040), (141200, 20000)]), + (16, 23, [(141220, 20040), (141220, 20060), (141280, 20060)]), + (16, 24, [(141220, 20040), (141240, 20040), (141240, 20000)]), + (17, 18, [(141220, 19960), (141200, 19960), (141200, 20000)]), + (17, 24, [(141220, 19960), (141240, 19960), (141240, 20000)]), + (17, 25, [(141220, 19960), (141220, 19940), (141280, 19940)]), + (18, 23, [(141300, 20000), (141300, 20060), (141280, 20060)]), + (18, 24, [(141300, 20000), (141240, 20000)]), + (18, 25, [(141300, 20000), (141300, 19940), (141280, 19940)])] + terminalVariables := [3, 10] + boundaryPorts := [(90000, 0), (0, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (21500, 10000), (21500, 19300), (21300, 19300), (21300, 20000), + (21200, 20000)], [(0, 90000), (10000, 90000), (10000, 100700), (18700, 100700), (18700, + 100000), (18800, 100000)]] + +/-- Fixed profile 5: source polarities `[true, false]`. -/ +def profileGeometry5 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (20000, 100000), + (70000, 100000), + (100000, 120000), + (140000, 20000), + (160000, 40000), + (141200, 20000), + (140600, 19600), + (141200, 18800), + (140600, 19200), + (140000, 18800), + (141280, 20060), + (141240, 20000), + (141280, 19940)] + clausePositions := + [(40000, 40000), + (40000, 100000), + (100000, 100000), + (140000, 40000), + (140600, 20000), + (141000, 19400), + (140200, 19400), + (140600, 18600), + (141220, 20040), + (141220, 19960), + (141300, 20000)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(40000, 100000), (20000, 100000)]), + (1, 1, [(40000, 100000), (40000, 60000)]), + (1, 4, [(40000, 100000), (70000, 100000)]), + (2, 2, [(100000, 100000), (100000, 40000)]), + (2, 4, [(100000, 100000), (70000, 100000)]), + (2, 5, [(100000, 100000), (100000, 120000)]), + (3, 6, [(140000, 40000), (140000, 20000)]), + (3, 2, [(140000, 40000), (100000, 40000)]), + (3, 7, [(140000, 40000), (160000, 40000)]), + (4, 8, [(140600, 20000), (141200, 20000)]), + (4, 6, [(140600, 20000), (140000, 20000)]), + (4, 9, [(140600, 20000), (140600, 19600)]), + (5, 9, [(141000, 19400), (141000, 19600), (140600, 19600)]), + (5, 10, [(141000, 19400), (141200, 19400), (141200, 18800)]), + (5, 11, [(141000, 19400), (141000, 19200), (140600, 19200)]), + (6, 9, [(140200, 19400), (140200, 19600), (140600, 19600)]), + (6, 11, [(140200, 19400), (140200, 19200), (140600, 19200)]), + (6, 12, [(140200, 19400), (140000, 19400), (140000, 18800)]), + (7, 10, [(140600, 18600), (141200, 18600), (141200, 18800)]), + (7, 11, [(140600, 18600), (140600, 19200)]), + (7, 12, [(140600, 18600), (140000, 18600), (140000, 18800)]), + (8, 8, [(141220, 20040), (141200, 20040), (141200, 20000)]), + (8, 13, [(141220, 20040), (141220, 20060), (141280, 20060)]), + (8, 14, [(141220, 20040), (141240, 20040), (141240, 20000)]), + (9, 8, [(141220, 19960), (141200, 19960), (141200, 20000)]), + (9, 14, [(141220, 19960), (141240, 19960), (141240, 20000)]), + (9, 15, [(141220, 19960), (141220, 19940), (141280, 19940)]), + (10, 13, [(141300, 20000), (141300, 20060), (141280, 20060)]), + (10, 14, [(141300, 20000), (141240, 20000)]), + (10, 15, [(141300, 20000), (141300, 19940), (141280, 19940)])] + terminalVariables := [0, 3] + boundaryPorts := [(90000, 0), (0, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (20000, 10000), (20000, 20000)], [(0, 90000), (10000, 90000), + (10000, 100000), (20000, 100000)]] + +/-- Fixed profile 6: source polarities `[true, true]`. -/ +def profileGeometry6 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (20000, 100000), + (70000, 100000), + (18800, 100000), + (19400, 100400), + (18800, 101200), + (19400, 100800), + (20000, 101200), + (100000, 120000), + (140000, 20000), + (160000, 40000), + (141200, 20000), + (140600, 19600), + (141200, 18800), + (140600, 19200), + (140000, 18800), + (141280, 20060), + (141240, 20000), + (141280, 19940)] + clausePositions := + [(40000, 40000), + (40000, 100000), + (19400, 100000), + (19000, 100600), + (19800, 100600), + (19400, 101400), + (100000, 100000), + (140000, 40000), + (140600, 20000), + (141000, 19400), + (140200, 19400), + (140600, 18600), + (141220, 20040), + (141220, 19960), + (141300, 20000)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(40000, 100000), (20000, 100000)]), + (1, 1, [(40000, 100000), (40000, 60000)]), + (1, 4, [(40000, 100000), (70000, 100000)]), + (2, 5, [(19400, 100000), (18800, 100000)]), + (2, 3, [(19400, 100000), (20000, 100000)]), + (2, 6, [(19400, 100000), (19400, 100400)]), + (3, 6, [(19000, 100600), (19000, 100400), (19400, 100400)]), + (3, 7, [(19000, 100600), (18800, 100600), (18800, 101200)]), + (3, 8, [(19000, 100600), (19000, 100800), (19400, 100800)]), + (4, 6, [(19800, 100600), (19800, 100400), (19400, 100400)]), + (4, 8, [(19800, 100600), (19800, 100800), (19400, 100800)]), + (4, 9, [(19800, 100600), (20000, 100600), (20000, 101200)]), + (5, 7, [(19400, 101400), (18800, 101400), (18800, 101200)]), + (5, 8, [(19400, 101400), (19400, 100800)]), + (5, 9, [(19400, 101400), (20000, 101400), (20000, 101200)]), + (6, 2, [(100000, 100000), (100000, 40000)]), + (6, 4, [(100000, 100000), (70000, 100000)]), + (6, 10, [(100000, 100000), (100000, 120000)]), + (7, 11, [(140000, 40000), (140000, 20000)]), + (7, 2, [(140000, 40000), (100000, 40000)]), + (7, 12, [(140000, 40000), (160000, 40000)]), + (8, 13, [(140600, 20000), (141200, 20000)]), + (8, 11, [(140600, 20000), (140000, 20000)]), + (8, 14, [(140600, 20000), (140600, 19600)]), + (9, 14, [(141000, 19400), (141000, 19600), (140600, 19600)]), + (9, 15, [(141000, 19400), (141200, 19400), (141200, 18800)]), + (9, 16, [(141000, 19400), (141000, 19200), (140600, 19200)]), + (10, 14, [(140200, 19400), (140200, 19600), (140600, 19600)]), + (10, 16, [(140200, 19400), (140200, 19200), (140600, 19200)]), + (10, 17, [(140200, 19400), (140000, 19400), (140000, 18800)]), + (11, 15, [(140600, 18600), (141200, 18600), (141200, 18800)]), + (11, 16, [(140600, 18600), (140600, 19200)]), + (11, 17, [(140600, 18600), (140000, 18600), (140000, 18800)]), + (12, 13, [(141220, 20040), (141200, 20040), (141200, 20000)]), + (12, 18, [(141220, 20040), (141220, 20060), (141280, 20060)]), + (12, 19, [(141220, 20040), (141240, 20040), (141240, 20000)]), + (13, 13, [(141220, 19960), (141200, 19960), (141200, 20000)]), + (13, 19, [(141220, 19960), (141240, 19960), (141240, 20000)]), + (13, 20, [(141220, 19960), (141220, 19940), (141280, 19940)]), + (14, 18, [(141300, 20000), (141300, 20060), (141280, 20060)]), + (14, 19, [(141300, 20000), (141240, 20000)]), + (14, 20, [(141300, 20000), (141300, 19940), (141280, 19940)])] + terminalVariables := [0, 5] + boundaryPorts := [(90000, 0), (0, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (20000, 10000), (20000, 20000)], [(0, 90000), (10000, 90000), + (10000, 100700), (18700, 100700), (18700, 100000), (18800, 100000)]] + +/-- Fixed profile 7: source polarities `[false, false, false]`. -/ +def profileGeometry7 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (21200, 20000), + (20600, 19600), + (21200, 18800), + (20600, 19200), + (20000, 18800), + (20000, 100000), + (70000, 100000), + (100000, 120000), + (140000, 20000), + (160000, 40000)] + clausePositions := + [(40000, 40000), + (20600, 20000), + (21000, 19400), + (20200, 19400), + (20600, 18600), + (40000, 100000), + (100000, 100000), + (140000, 40000)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(20600, 20000), (21200, 20000)]), + (1, 0, [(20600, 20000), (20000, 20000)]), + (1, 4, [(20600, 20000), (20600, 19600)]), + (2, 4, [(21000, 19400), (21000, 19600), (20600, 19600)]), + (2, 5, [(21000, 19400), (21200, 19400), (21200, 18800)]), + (2, 6, [(21000, 19400), (21000, 19200), (20600, 19200)]), + (3, 4, [(20200, 19400), (20200, 19600), (20600, 19600)]), + (3, 6, [(20200, 19400), (20200, 19200), (20600, 19200)]), + (3, 7, [(20200, 19400), (20000, 19400), (20000, 18800)]), + (4, 5, [(20600, 18600), (21200, 18600), (21200, 18800)]), + (4, 6, [(20600, 18600), (20600, 19200)]), + (4, 7, [(20600, 18600), (20000, 18600), (20000, 18800)]), + (5, 8, [(40000, 100000), (20000, 100000)]), + (5, 1, [(40000, 100000), (40000, 60000)]), + (5, 9, [(40000, 100000), (70000, 100000)]), + (6, 2, [(100000, 100000), (100000, 40000)]), + (6, 9, [(100000, 100000), (70000, 100000)]), + (6, 10, [(100000, 100000), (100000, 120000)]), + (7, 11, [(140000, 40000), (140000, 20000)]), + (7, 2, [(140000, 40000), (100000, 40000)]), + (7, 12, [(140000, 40000), (160000, 40000)])] + terminalVariables := [3, 8, 11] + boundaryPorts := [(90000, 0), (0, 90000), (180000, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (21500, 10000), (21500, 19300), (21300, 19300), (21300, 20000), + (21200, 20000)], [(0, 90000), (10000, 90000), (10000, 100000), (20000, 100000)], [(180000, + 90000), (170000, 90000), (170000, 20000), (140000, 20000)]] + +/-- Fixed profile 8: source polarities `[false, false, true]`. -/ +def profileGeometry8 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (21200, 20000), + (20600, 19600), + (21200, 18800), + (20600, 19200), + (20000, 18800), + (20000, 100000), + (70000, 100000), + (100000, 120000), + (140000, 20000), + (160000, 40000), + (141200, 20000), + (140600, 19600), + (141200, 18800), + (140600, 19200), + (140000, 18800)] + clausePositions := + [(40000, 40000), + (20600, 20000), + (21000, 19400), + (20200, 19400), + (20600, 18600), + (40000, 100000), + (100000, 100000), + (140000, 40000), + (140600, 20000), + (141000, 19400), + (140200, 19400), + (140600, 18600)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(20600, 20000), (21200, 20000)]), + (1, 0, [(20600, 20000), (20000, 20000)]), + (1, 4, [(20600, 20000), (20600, 19600)]), + (2, 4, [(21000, 19400), (21000, 19600), (20600, 19600)]), + (2, 5, [(21000, 19400), (21200, 19400), (21200, 18800)]), + (2, 6, [(21000, 19400), (21000, 19200), (20600, 19200)]), + (3, 4, [(20200, 19400), (20200, 19600), (20600, 19600)]), + (3, 6, [(20200, 19400), (20200, 19200), (20600, 19200)]), + (3, 7, [(20200, 19400), (20000, 19400), (20000, 18800)]), + (4, 5, [(20600, 18600), (21200, 18600), (21200, 18800)]), + (4, 6, [(20600, 18600), (20600, 19200)]), + (4, 7, [(20600, 18600), (20000, 18600), (20000, 18800)]), + (5, 8, [(40000, 100000), (20000, 100000)]), + (5, 1, [(40000, 100000), (40000, 60000)]), + (5, 9, [(40000, 100000), (70000, 100000)]), + (6, 2, [(100000, 100000), (100000, 40000)]), + (6, 9, [(100000, 100000), (70000, 100000)]), + (6, 10, [(100000, 100000), (100000, 120000)]), + (7, 11, [(140000, 40000), (140000, 20000)]), + (7, 2, [(140000, 40000), (100000, 40000)]), + (7, 12, [(140000, 40000), (160000, 40000)]), + (8, 13, [(140600, 20000), (141200, 20000)]), + (8, 11, [(140600, 20000), (140000, 20000)]), + (8, 14, [(140600, 20000), (140600, 19600)]), + (9, 14, [(141000, 19400), (141000, 19600), (140600, 19600)]), + (9, 15, [(141000, 19400), (141200, 19400), (141200, 18800)]), + (9, 16, [(141000, 19400), (141000, 19200), (140600, 19200)]), + (10, 14, [(140200, 19400), (140200, 19600), (140600, 19600)]), + (10, 16, [(140200, 19400), (140200, 19200), (140600, 19200)]), + (10, 17, [(140200, 19400), (140000, 19400), (140000, 18800)]), + (11, 15, [(140600, 18600), (141200, 18600), (141200, 18800)]), + (11, 16, [(140600, 18600), (140600, 19200)]), + (11, 17, [(140600, 18600), (140000, 18600), (140000, 18800)])] + terminalVariables := [3, 8, 13] + boundaryPorts := [(90000, 0), (0, 90000), (180000, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (21500, 10000), (21500, 19300), (21300, 19300), (21300, 20000), + (21200, 20000)], [(0, 90000), (10000, 90000), (10000, 100000), (20000, 100000)], [(180000, + 90000), (170000, 90000), (170000, 19300), (141300, 19300), (141300, 20000), (141200, + 20000)]] + +/-- Fixed profile 9: source polarities `[false, true, false]`. -/ +def profileGeometry9 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (21200, 20000), + (20600, 19600), + (21200, 18800), + (20600, 19200), + (20000, 18800), + (20000, 100000), + (70000, 100000), + (18800, 100000), + (19400, 100400), + (18800, 101200), + (19400, 100800), + (20000, 101200), + (100000, 120000), + (140000, 20000), + (160000, 40000)] + clausePositions := + [(40000, 40000), + (20600, 20000), + (21000, 19400), + (20200, 19400), + (20600, 18600), + (40000, 100000), + (19400, 100000), + (19000, 100600), + (19800, 100600), + (19400, 101400), + (100000, 100000), + (140000, 40000)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(20600, 20000), (21200, 20000)]), + (1, 0, [(20600, 20000), (20000, 20000)]), + (1, 4, [(20600, 20000), (20600, 19600)]), + (2, 4, [(21000, 19400), (21000, 19600), (20600, 19600)]), + (2, 5, [(21000, 19400), (21200, 19400), (21200, 18800)]), + (2, 6, [(21000, 19400), (21000, 19200), (20600, 19200)]), + (3, 4, [(20200, 19400), (20200, 19600), (20600, 19600)]), + (3, 6, [(20200, 19400), (20200, 19200), (20600, 19200)]), + (3, 7, [(20200, 19400), (20000, 19400), (20000, 18800)]), + (4, 5, [(20600, 18600), (21200, 18600), (21200, 18800)]), + (4, 6, [(20600, 18600), (20600, 19200)]), + (4, 7, [(20600, 18600), (20000, 18600), (20000, 18800)]), + (5, 8, [(40000, 100000), (20000, 100000)]), + (5, 1, [(40000, 100000), (40000, 60000)]), + (5, 9, [(40000, 100000), (70000, 100000)]), + (6, 10, [(19400, 100000), (18800, 100000)]), + (6, 8, [(19400, 100000), (20000, 100000)]), + (6, 11, [(19400, 100000), (19400, 100400)]), + (7, 11, [(19000, 100600), (19000, 100400), (19400, 100400)]), + (7, 12, [(19000, 100600), (18800, 100600), (18800, 101200)]), + (7, 13, [(19000, 100600), (19000, 100800), (19400, 100800)]), + (8, 11, [(19800, 100600), (19800, 100400), (19400, 100400)]), + (8, 13, [(19800, 100600), (19800, 100800), (19400, 100800)]), + (8, 14, [(19800, 100600), (20000, 100600), (20000, 101200)]), + (9, 12, [(19400, 101400), (18800, 101400), (18800, 101200)]), + (9, 13, [(19400, 101400), (19400, 100800)]), + (9, 14, [(19400, 101400), (20000, 101400), (20000, 101200)]), + (10, 2, [(100000, 100000), (100000, 40000)]), + (10, 9, [(100000, 100000), (70000, 100000)]), + (10, 15, [(100000, 100000), (100000, 120000)]), + (11, 16, [(140000, 40000), (140000, 20000)]), + (11, 2, [(140000, 40000), (100000, 40000)]), + (11, 17, [(140000, 40000), (160000, 40000)])] + terminalVariables := [3, 10, 16] + boundaryPorts := [(90000, 0), (0, 90000), (180000, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (21500, 10000), (21500, 19300), (21300, 19300), (21300, 20000), + (21200, 20000)], [(0, 90000), (10000, 90000), (10000, 100700), (18700, 100700), (18700, + 100000), (18800, 100000)], [(180000, 90000), (170000, 90000), (170000, 20000), (140000, + 20000)]] + +/-- Fixed profile 10: source polarities `[false, true, true]`. -/ +def profileGeometry10 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (21200, 20000), + (20600, 19600), + (21200, 18800), + (20600, 19200), + (20000, 18800), + (20000, 100000), + (70000, 100000), + (18800, 100000), + (19400, 100400), + (18800, 101200), + (19400, 100800), + (20000, 101200), + (100000, 120000), + (140000, 20000), + (160000, 40000), + (141200, 20000), + (140600, 19600), + (141200, 18800), + (140600, 19200), + (140000, 18800)] + clausePositions := + [(40000, 40000), + (20600, 20000), + (21000, 19400), + (20200, 19400), + (20600, 18600), + (40000, 100000), + (19400, 100000), + (19000, 100600), + (19800, 100600), + (19400, 101400), + (100000, 100000), + (140000, 40000), + (140600, 20000), + (141000, 19400), + (140200, 19400), + (140600, 18600)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(20600, 20000), (21200, 20000)]), + (1, 0, [(20600, 20000), (20000, 20000)]), + (1, 4, [(20600, 20000), (20600, 19600)]), + (2, 4, [(21000, 19400), (21000, 19600), (20600, 19600)]), + (2, 5, [(21000, 19400), (21200, 19400), (21200, 18800)]), + (2, 6, [(21000, 19400), (21000, 19200), (20600, 19200)]), + (3, 4, [(20200, 19400), (20200, 19600), (20600, 19600)]), + (3, 6, [(20200, 19400), (20200, 19200), (20600, 19200)]), + (3, 7, [(20200, 19400), (20000, 19400), (20000, 18800)]), + (4, 5, [(20600, 18600), (21200, 18600), (21200, 18800)]), + (4, 6, [(20600, 18600), (20600, 19200)]), + (4, 7, [(20600, 18600), (20000, 18600), (20000, 18800)]), + (5, 8, [(40000, 100000), (20000, 100000)]), + (5, 1, [(40000, 100000), (40000, 60000)]), + (5, 9, [(40000, 100000), (70000, 100000)]), + (6, 10, [(19400, 100000), (18800, 100000)]), + (6, 8, [(19400, 100000), (20000, 100000)]), + (6, 11, [(19400, 100000), (19400, 100400)]), + (7, 11, [(19000, 100600), (19000, 100400), (19400, 100400)]), + (7, 12, [(19000, 100600), (18800, 100600), (18800, 101200)]), + (7, 13, [(19000, 100600), (19000, 100800), (19400, 100800)]), + (8, 11, [(19800, 100600), (19800, 100400), (19400, 100400)]), + (8, 13, [(19800, 100600), (19800, 100800), (19400, 100800)]), + (8, 14, [(19800, 100600), (20000, 100600), (20000, 101200)]), + (9, 12, [(19400, 101400), (18800, 101400), (18800, 101200)]), + (9, 13, [(19400, 101400), (19400, 100800)]), + (9, 14, [(19400, 101400), (20000, 101400), (20000, 101200)]), + (10, 2, [(100000, 100000), (100000, 40000)]), + (10, 9, [(100000, 100000), (70000, 100000)]), + (10, 15, [(100000, 100000), (100000, 120000)]), + (11, 16, [(140000, 40000), (140000, 20000)]), + (11, 2, [(140000, 40000), (100000, 40000)]), + (11, 17, [(140000, 40000), (160000, 40000)]), + (12, 18, [(140600, 20000), (141200, 20000)]), + (12, 16, [(140600, 20000), (140000, 20000)]), + (12, 19, [(140600, 20000), (140600, 19600)]), + (13, 19, [(141000, 19400), (141000, 19600), (140600, 19600)]), + (13, 20, [(141000, 19400), (141200, 19400), (141200, 18800)]), + (13, 21, [(141000, 19400), (141000, 19200), (140600, 19200)]), + (14, 19, [(140200, 19400), (140200, 19600), (140600, 19600)]), + (14, 21, [(140200, 19400), (140200, 19200), (140600, 19200)]), + (14, 22, [(140200, 19400), (140000, 19400), (140000, 18800)]), + (15, 20, [(140600, 18600), (141200, 18600), (141200, 18800)]), + (15, 21, [(140600, 18600), (140600, 19200)]), + (15, 22, [(140600, 18600), (140000, 18600), (140000, 18800)])] + terminalVariables := [3, 10, 18] + boundaryPorts := [(90000, 0), (0, 90000), (180000, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (21500, 10000), (21500, 19300), (21300, 19300), (21300, 20000), + (21200, 20000)], [(0, 90000), (10000, 90000), (10000, 100700), (18700, 100700), (18700, + 100000), (18800, 100000)], [(180000, 90000), (170000, 90000), (170000, 19300), (141300, + 19300), (141300, 20000), (141200, 20000)]] + +/-- Fixed profile 11: source polarities `[true, false, false]`. -/ +def profileGeometry11 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (20000, 100000), + (70000, 100000), + (100000, 120000), + (140000, 20000), + (160000, 40000)] + clausePositions := + [(40000, 40000), + (40000, 100000), + (100000, 100000), + (140000, 40000)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(40000, 100000), (20000, 100000)]), + (1, 1, [(40000, 100000), (40000, 60000)]), + (1, 4, [(40000, 100000), (70000, 100000)]), + (2, 2, [(100000, 100000), (100000, 40000)]), + (2, 4, [(100000, 100000), (70000, 100000)]), + (2, 5, [(100000, 100000), (100000, 120000)]), + (3, 6, [(140000, 40000), (140000, 20000)]), + (3, 2, [(140000, 40000), (100000, 40000)]), + (3, 7, [(140000, 40000), (160000, 40000)])] + terminalVariables := [0, 3, 6] + boundaryPorts := [(90000, 0), (0, 90000), (180000, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (20000, 10000), (20000, 20000)], [(0, 90000), (10000, 90000), + (10000, 100000), (20000, 100000)], [(180000, 90000), (170000, 90000), (170000, 20000), + (140000, 20000)]] + +/-- Fixed profile 12: source polarities `[true, false, true]`. -/ +def profileGeometry12 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (20000, 100000), + (70000, 100000), + (100000, 120000), + (140000, 20000), + (160000, 40000), + (141200, 20000), + (140600, 19600), + (141200, 18800), + (140600, 19200), + (140000, 18800)] + clausePositions := + [(40000, 40000), + (40000, 100000), + (100000, 100000), + (140000, 40000), + (140600, 20000), + (141000, 19400), + (140200, 19400), + (140600, 18600)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(40000, 100000), (20000, 100000)]), + (1, 1, [(40000, 100000), (40000, 60000)]), + (1, 4, [(40000, 100000), (70000, 100000)]), + (2, 2, [(100000, 100000), (100000, 40000)]), + (2, 4, [(100000, 100000), (70000, 100000)]), + (2, 5, [(100000, 100000), (100000, 120000)]), + (3, 6, [(140000, 40000), (140000, 20000)]), + (3, 2, [(140000, 40000), (100000, 40000)]), + (3, 7, [(140000, 40000), (160000, 40000)]), + (4, 8, [(140600, 20000), (141200, 20000)]), + (4, 6, [(140600, 20000), (140000, 20000)]), + (4, 9, [(140600, 20000), (140600, 19600)]), + (5, 9, [(141000, 19400), (141000, 19600), (140600, 19600)]), + (5, 10, [(141000, 19400), (141200, 19400), (141200, 18800)]), + (5, 11, [(141000, 19400), (141000, 19200), (140600, 19200)]), + (6, 9, [(140200, 19400), (140200, 19600), (140600, 19600)]), + (6, 11, [(140200, 19400), (140200, 19200), (140600, 19200)]), + (6, 12, [(140200, 19400), (140000, 19400), (140000, 18800)]), + (7, 10, [(140600, 18600), (141200, 18600), (141200, 18800)]), + (7, 11, [(140600, 18600), (140600, 19200)]), + (7, 12, [(140600, 18600), (140000, 18600), (140000, 18800)])] + terminalVariables := [0, 3, 8] + boundaryPorts := [(90000, 0), (0, 90000), (180000, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (20000, 10000), (20000, 20000)], [(0, 90000), (10000, 90000), + (10000, 100000), (20000, 100000)], [(180000, 90000), (170000, 90000), (170000, 19300), + (141300, 19300), (141300, 20000), (141200, 20000)]] + +/-- Fixed profile 13: source polarities `[true, true, false]`. -/ +def profileGeometry13 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (20000, 100000), + (70000, 100000), + (18800, 100000), + (19400, 100400), + (18800, 101200), + (19400, 100800), + (20000, 101200), + (100000, 120000), + (140000, 20000), + (160000, 40000)] + clausePositions := + [(40000, 40000), + (40000, 100000), + (19400, 100000), + (19000, 100600), + (19800, 100600), + (19400, 101400), + (100000, 100000), + (140000, 40000)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(40000, 100000), (20000, 100000)]), + (1, 1, [(40000, 100000), (40000, 60000)]), + (1, 4, [(40000, 100000), (70000, 100000)]), + (2, 5, [(19400, 100000), (18800, 100000)]), + (2, 3, [(19400, 100000), (20000, 100000)]), + (2, 6, [(19400, 100000), (19400, 100400)]), + (3, 6, [(19000, 100600), (19000, 100400), (19400, 100400)]), + (3, 7, [(19000, 100600), (18800, 100600), (18800, 101200)]), + (3, 8, [(19000, 100600), (19000, 100800), (19400, 100800)]), + (4, 6, [(19800, 100600), (19800, 100400), (19400, 100400)]), + (4, 8, [(19800, 100600), (19800, 100800), (19400, 100800)]), + (4, 9, [(19800, 100600), (20000, 100600), (20000, 101200)]), + (5, 7, [(19400, 101400), (18800, 101400), (18800, 101200)]), + (5, 8, [(19400, 101400), (19400, 100800)]), + (5, 9, [(19400, 101400), (20000, 101400), (20000, 101200)]), + (6, 2, [(100000, 100000), (100000, 40000)]), + (6, 4, [(100000, 100000), (70000, 100000)]), + (6, 10, [(100000, 100000), (100000, 120000)]), + (7, 11, [(140000, 40000), (140000, 20000)]), + (7, 2, [(140000, 40000), (100000, 40000)]), + (7, 12, [(140000, 40000), (160000, 40000)])] + terminalVariables := [0, 5, 11] + boundaryPorts := [(90000, 0), (0, 90000), (180000, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (20000, 10000), (20000, 20000)], [(0, 90000), (10000, 90000), + (10000, 100700), (18700, 100700), (18700, 100000), (18800, 100000)], [(180000, 90000), + (170000, 90000), (170000, 20000), (140000, 20000)]] + +/-- Fixed profile 14: source polarities `[true, true, true]`. -/ +def profileGeometry14 : LocalGeometry where + size := 180000 + variablePositions := + [(20000, 20000), + (40000, 60000), + (100000, 40000), + (20000, 100000), + (70000, 100000), + (18800, 100000), + (19400, 100400), + (18800, 101200), + (19400, 100800), + (20000, 101200), + (100000, 120000), + (140000, 20000), + (160000, 40000), + (141200, 20000), + (140600, 19600), + (141200, 18800), + (140600, 19200), + (140000, 18800)] + clausePositions := + [(40000, 40000), + (40000, 100000), + (19400, 100000), + (19000, 100600), + (19800, 100600), + (19400, 101400), + (100000, 100000), + (140000, 40000), + (140600, 20000), + (141000, 19400), + (140200, 19400), + (140600, 18600)] + incidenceRoutes := + [(0, 0, [(40000, 40000), (20000, 40000), (20000, 20000)]), + (0, 1, [(40000, 40000), (40000, 60000)]), + (0, 2, [(40000, 40000), (100000, 40000)]), + (1, 3, [(40000, 100000), (20000, 100000)]), + (1, 1, [(40000, 100000), (40000, 60000)]), + (1, 4, [(40000, 100000), (70000, 100000)]), + (2, 5, [(19400, 100000), (18800, 100000)]), + (2, 3, [(19400, 100000), (20000, 100000)]), + (2, 6, [(19400, 100000), (19400, 100400)]), + (3, 6, [(19000, 100600), (19000, 100400), (19400, 100400)]), + (3, 7, [(19000, 100600), (18800, 100600), (18800, 101200)]), + (3, 8, [(19000, 100600), (19000, 100800), (19400, 100800)]), + (4, 6, [(19800, 100600), (19800, 100400), (19400, 100400)]), + (4, 8, [(19800, 100600), (19800, 100800), (19400, 100800)]), + (4, 9, [(19800, 100600), (20000, 100600), (20000, 101200)]), + (5, 7, [(19400, 101400), (18800, 101400), (18800, 101200)]), + (5, 8, [(19400, 101400), (19400, 100800)]), + (5, 9, [(19400, 101400), (20000, 101400), (20000, 101200)]), + (6, 2, [(100000, 100000), (100000, 40000)]), + (6, 4, [(100000, 100000), (70000, 100000)]), + (6, 10, [(100000, 100000), (100000, 120000)]), + (7, 11, [(140000, 40000), (140000, 20000)]), + (7, 2, [(140000, 40000), (100000, 40000)]), + (7, 12, [(140000, 40000), (160000, 40000)]), + (8, 13, [(140600, 20000), (141200, 20000)]), + (8, 11, [(140600, 20000), (140000, 20000)]), + (8, 14, [(140600, 20000), (140600, 19600)]), + (9, 14, [(141000, 19400), (141000, 19600), (140600, 19600)]), + (9, 15, [(141000, 19400), (141200, 19400), (141200, 18800)]), + (9, 16, [(141000, 19400), (141000, 19200), (140600, 19200)]), + (10, 14, [(140200, 19400), (140200, 19600), (140600, 19600)]), + (10, 16, [(140200, 19400), (140200, 19200), (140600, 19200)]), + (10, 17, [(140200, 19400), (140000, 19400), (140000, 18800)]), + (11, 15, [(140600, 18600), (141200, 18600), (141200, 18800)]), + (11, 16, [(140600, 18600), (140600, 19200)]), + (11, 17, [(140600, 18600), (140000, 18600), (140000, 18800)])] + terminalVariables := [0, 5, 13] + boundaryPorts := [(90000, 0), (0, 90000), (180000, 90000)] + accessRoutes := + [[(90000, 0), (90000, 10000), (20000, 10000), (20000, 20000)], [(0, 90000), (10000, 90000), + (10000, 100700), (18700, 100700), (18700, 100000), (18800, 100000)], [(180000, 90000), + (170000, 90000), (170000, 19300), (141300, 19300), (141300, 20000), (141200, 20000)]] + +/-- Select the fixed geometric data for the complete source polarity profile. -/ +def profileGeometry (p : ClauseProfile) : LocalGeometry := + match p.val with + | 0 => profileGeometry0 + | 1 => profileGeometry1 + | 2 => profileGeometry2 + | 3 => profileGeometry3 + | 4 => profileGeometry4 + | 5 => profileGeometry5 + | 6 => profileGeometry6 + | 7 => profileGeometry7 + | 8 => profileGeometry8 + | 9 => profileGeometry9 + | 10 => profileGeometry10 + | 11 => profileGeometry11 + | 12 => profileGeometry12 + | 13 => profileGeometry13 + | 14 => profileGeometry14 + | _ => profileGeometry0 + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesPrefix.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesPrefix.lean new file mode 100644 index 000000000..7a97f93c2 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesPrefix.lean @@ -0,0 +1,198 @@ +/- +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.ExactOne.GeometryProfiles +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesPrefixData + +/-! +# Certified source-boundary prefixes for positive exact-one profiles + +The actual fifteen positive replacement profiles have their source incidences extended to the +cell boundary. The resulting finite certificates include all full real route contacts, simplicity, +nonincident vertices and the isolation of the cell boundary. + +## Main definitions + +* `profilePrefixGeometry`: the actual profile with source endpoints at boundary ports. + +## Main results + +* `profilePrefixGeometry_check`: full continuous certificates for all fifteen boundary profiles. +* `profilePrefixGeometry_signature`: exact alignment with the positive formula compiler. + +## References + +These are original geometric certificates for the cited exact-one clause constructions. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Orthogonal + +-- Each closed certificate checks all consecutive-segment pairs in a table of at most +-- sixty-six incidences. Separate declarations permit independent asynchronous elaboration. +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile0_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (0 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile1_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (1 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile2_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (2 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile3_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (3 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile4_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (4 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile5_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (5 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile6_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (6 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile7_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (7 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile8_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (8 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile9_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (9 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile10_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (10 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile11_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (11 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile12_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (12 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile13_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (13 : ClauseProfile)) := by + decide +kernel + +set_option maxHeartbeats 8000000 in +-- Exhaustive kernel checking of a fixed table with at most sixty-six incidence routes. +private theorem profile14_prefix_check : + LocalGeometry.PrefixCheck (profilePrefixGeometry (14 : ClauseProfile)) := by + decide +kernel + +/-- All fifteen source-boundary prefix drawings have full continuous certificates. -/ +theorem profilePrefixGeometry_check (p : ClauseProfile) : + LocalGeometry.PrefixCheck (profilePrefixGeometry p) := by + fin_cases p + · exact profile0_prefix_check + · exact profile1_prefix_check + · exact profile2_prefix_check + · exact profile3_prefix_check + · exact profile4_prefix_check + · exact profile5_prefix_check + · exact profile6_prefix_check + · exact profile7_prefix_check + · exact profile8_prefix_check + · exact profile9_prefix_check + · exact profile10_prefix_check + · exact profile11_prefix_check + · exact profile12_prefix_check + · exact profile13_prefix_check + · exact profile14_prefix_check + +/-- Extending source incidences preserves the exact logical incidence signature. -/ +theorem profilePrefixGeometry_signature (p : ClauseProfile) : + ((profilePrefixGeometry p).incidenceRoutes.map fun e => (e.1, e.2.1)) = + profileSignature p := by + rw [← profileGeometry_signature] + simp [profilePrefixGeometry, LocalGeometry.toPrefix, List.map_map, Function.comp_def] + +/-- Each exposed source position is exactly its assigned boundary port after extension. -/ +theorem profilePrefixGeometry_terminal_position (p : ClauseProfile) : + ∀ i : Fin (profileSource p).length, + (profilePrefixGeometry p).variablePosition + ((profileGeometry p).terminalVariables.getD i 0) = + (profileGeometry p).boundaryPorts.getD i (0, 0) := by + fin_cases p <;> decide +kernel + +/-- All auxiliary variables remain strictly inside the profile cell. -/ +theorem profilePrefixGeometry_auxiliary_interior (p : ClauseProfile) : + ∀ i : Fin (profilePrefixGeometry p).variablePositions.length, + i.val ∉ (profilePrefixGeometry p).terminalVariables → + let q := (profilePrefixGeometry p).variablePosition i + 0 < q.1 ∧ q.1 < 180000 ∧ 0 < q.2 ∧ q.2 < 180000 := by + fin_cases p <;> decide +kernel + +/-- All actual clause vertices remain strictly inside the profile cell. -/ +theorem profilePrefixGeometry_clause_interior (p : ClauseProfile) : + ∀ q ∈ (profilePrefixGeometry p).clausePositions, + 0 < q.1 ∧ q.1 < 180000 ∧ 0 < q.2 ∧ q.2 < 180000 := by + fin_cases p <;> decide +kernel + +/-- Source-boundary extension preserves the single incidence at every source placeholder. -/ +theorem profilePrefixGeometry_terminal_degree (p : ClauseProfile) : + ∀ v ∈ (profilePrefixGeometry p).terminalVariables, + ((profilePrefixGeometry p).incidenceRoutes.map fun e => e.2.1).count v = 1 := by + simpa [profilePrefixGeometry, LocalGeometry.toPrefix, List.map_map, Function.comp_def] using + profileGeometry_terminal_degree p + +/-- The actual finite prefix tables have constant vertex, route, segment and wire costs. -/ +theorem profilePrefixGeometry_costs (p : ClauseProfile) : + (profilePrefixGeometry p).variablePositions.length ≤ 29 ∧ + (profilePrefixGeometry p).clausePositions.length ≤ 22 ∧ + (profilePrefixGeometry p).incidenceRoutes.length ≤ 66 ∧ + (((profilePrefixGeometry p).incidenceRoutes.map fun e => e.2.2.length - 1).sum ≤ 113) ∧ + (((profilePrefixGeometry p).incidenceRoutes.map fun e => wireLength e.2.2).sum ≤ + 652600) ∧ (profilePrefixGeometry p).size = 180000 := by + fin_cases p <;> decide +kernel + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesPrefixData.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesPrefixData.lean new file mode 100644 index 000000000..2b8a7e8ae --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesPrefixData.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.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesData +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryPrefix + +/-! +# Actual source-boundary prefix data for positive exact-one profiles + +The source placeholders in each fixed positive profile are moved to their boundary ports and +their sole incidence routes are extended accordingly. The large finite continuous certificates +are separate, in `GeometryProfilesPrefix`. + +## Main definitions + +* `profilePrefixGeometry`: the actual profile with source endpoints at boundary ports. + +## References + +The composed geometric data are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +/-- The actual positive profile with its source ends extended to the cell boundary. -/ +def profilePrefixGeometry (p : ClauseProfile) : LocalGeometry := (profileGeometry p).toPrefix + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Foundations/Geometry/Orthogonal/Directions.lean b/Complexity/Foundations/Geometry/Orthogonal/Directions.lean new file mode 100644 index 000000000..a3648db34 --- /dev/null +++ b/Complexity/Foundations/Geometry/Orthogonal/Directions.lean @@ -0,0 +1,85 @@ +/- +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.Trim +import Mathlib.Tactic.NormNum + +/-! +# Distinct exit directions at orthogonal drawing vertices + +Two proper integer segments leaving the same vertex in the same cardinal direction overlap +at a nonintegral real point. Thus incidence routes whose contacts are graph vertices must +have distinct directions at any common initial vertex, including parallel listed incidences. + +## Main results + +* `nonGrid_contact_of_direction_eq`: a common direction forces a nonintegral intersection. +* `direction_ne_of_grid_contacts`: endpoint-only geometric contacts imply distinct exits. + +## References + +The elementary cardinal-ray separation and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Orthogonal + + +private theorem cutPoint_two_one_ne_even (a b v : Point) : cutPoint 2 1 a b ≠ 2 • v := by + have hd := direction_unit a b + rcases a with ⟨ax, ay⟩ + rcases v with ⟨vx, vy⟩ + intro h + rcases hd with hd | hd | hd | hd + all_goals + simp only [cutPoint, hd, one_smul, Prod.smul_mk, Int.nsmul_eq_mul, + Prod.mk_add_mk, Prod.mk.injEq] at h + omega + +/-- Equal cardinal directions force a common point that is not an integer grid point. -/ +theorem nonGrid_contact_of_direction_eq {a b c : Point} + (hab : AxisAligned a b) (hac : AxisAligned a c) (hd : direction a b = direction a c) : + ∃ p ∈ segment a b, p ∈ segment a c ∧ ∀ v : Point, p ≠ realPoint v := by + let q := cutPoint 2 1 a b + let p : ℝ × ℝ := (1 / 2 : ℝ) • realPoint q + have he : (2 : ℝ) • p = realPoint q := by + dsimp only [p] + rw [smul_smul] + norm_num + have hq₁ : realPoint q ∈ segment (2 • a) (2 • b) := + cutPoint_mem_segment hab (by decide) + have hq₂ : realPoint q ∈ segment (2 • a) (2 • c) := by + have heq : cutPoint 2 1 a b = cutPoint 2 1 a c := by simp only [cutPoint, hd] + change realPoint (cutPoint 2 1 a b) ∈ segment (2 • a) (2 • c) + rw [heq] + exact cutPoint_mem_segment hac (by decide) + have hp₁ : p ∈ segment a b := (mem_segment_nsmul_iff (by decide : 2 ≠ 0) a b p).1 + (by simpa only [Nat.cast_ofNat, he] using hq₁) + have hp₂ : p ∈ segment a c := (mem_segment_nsmul_iff (by decide : 2 ≠ 0) a c p).1 + (by simpa only [Nat.cast_ofNat, he] using hq₂) + refine ⟨p, hp₁, hp₂, ?_⟩ + intro v hv + have hv' : realPoint q = realPoint (2 • v) := by + rw [← he, hv, realPoint_nsmul] + norm_num + exact cutPoint_two_one_ne_even a b v (realPoint_injective hv') + +/-- If common segment points must be integer graph vertices, two incidences cannot leave +their shared initial vertex in the same direction. -/ +theorem direction_ne_of_grid_contacts {a b c : Point} + (hab : AxisAligned a b) (hac : AxisAligned a c) + (hc : ∀ p ∈ segment a b, p ∈ segment a c → ∃ v : Point, p = realPoint v) : + direction a b ≠ direction a c := by + intro hd + obtain ⟨p, hp, hq, hng⟩ := nonGrid_contact_of_direction_eq hab hac hd + obtain ⟨v, hv⟩ := hc p hp hq + exact hng v hv + +end Cslib.Orthogonal diff --git a/Complexity/Foundations/Geometry/Orthogonal/Isolation.lean b/Complexity/Foundations/Geometry/Orthogonal/Isolation.lean new file mode 100644 index 000000000..1fb4ffc0c --- /dev/null +++ b/Complexity/Foundations/Geometry/Orthogonal/Isolation.lean @@ -0,0 +1,218 @@ +/- +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.Subdivision +import Mathlib.Analysis.Convex.Basic +import Mathlib.Tactic.Linarith + +/-! +# Integer-grid isolation for local drawing replacements + +An orthogonal segment with integer endpoints that enters the open unit box about an integer +point must contain that integer point. In a simple route, only the initial segment can contain +its initial vertex. These facts supply uniform local isolation without searching for a +drawing-dependent real radius. + +## Main definitions + +* `nearGridPoint`: the open unit square centered on an integer point. + +## Main results + +* `gridPoint_mem_segment_of_near`, `gridPoint_mem_support_of_near`. +* `IsSimple.segment_eq_first_of_head_mem`, `IsSimple.mem_first_segment_of_near_head`. + +## References + +These integer-grid separation observations and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Orthogonal + + +/-- The open unit square centered on an integer grid point. -/ +def nearGridPoint (c : Point) (p : ℝ × ℝ) : Prop := + (c.1 : ℝ) - 1 < p.1 ∧ p.1 < (c.1 : ℝ) + 1 ∧ + (c.2 : ℝ) - 1 < p.2 ∧ p.2 < (c.2 : ℝ) + 1 + +private theorem int_between_of_near {a b c : ℤ} {x : ℝ} + (ha : (a : ℝ) ≤ x) (hb : x ≤ (b : ℝ)) + (hc : (c : ℝ) - 1 < x ∧ x < (c : ℝ) + 1) : a ≤ c ∧ c ≤ b := by + constructor + · by_contra h + have h' : c + 1 ≤ a := by omega + have h'' : (c : ℝ) + 1 ≤ (a : ℝ) := by exact_mod_cast h' + linarith [hc.2] + · by_contra h + have h' : b ≤ c - 1 := by omega + have h'' : (b : ℝ) ≤ (c : ℝ) - 1 := by exact_mod_cast h' + linarith [hc.1] + +/-- Distinct grid points are not within the open unit box about one another. -/ +theorem eq_of_nearGridPoint {a c : Point} (h : nearGridPoint c (realPoint a)) : a = c := by + have hx := int_between_of_near (a := a.1) (b := a.1) le_rfl le_rfl ⟨h.1, h.2.1⟩ + have hy := int_between_of_near (a := a.2) (b := a.2) le_rfl le_rfl ⟨h.2.2.1, h.2.2.2⟩ + exact Prod.ext (le_antisymm hx.1 hx.2) (le_antisymm hy.1 hy.2) + +/-- Entering the open unit box about a grid point forces an integer orthogonal segment +to contain that grid point. The statement concerns all real points of the segment. -/ +theorem gridPoint_mem_segment_of_near {a b c : Point} (hab : a.1 = b.1 ∨ a.2 = b.2) + {p : ℝ × ℝ} (hp : p ∈ segment a b) (hc : nearGridPoint c p) : + realPoint c ∈ segment a b := by + have hb := mem_segment_bounds hp + change (min (a.1 : ℝ) (b.1 : ℝ) ≤ p.1 ∧ p.1 ≤ max (a.1 : ℝ) (b.1 : ℝ)) ∧ + (min (a.2 : ℝ) (b.2 : ℝ) ≤ p.2 ∧ p.2 ≤ max (a.2 : ℝ) (b.2 : ℝ)) at hb + have hx : ((min a.1 b.1 : ℤ) : ℝ) ≤ p.1 ∧ + p.1 ≤ ((max a.1 b.1 : ℤ) : ℝ) := by + simpa only [Int.cast_min, Int.cast_max] using hb.1 + have hy : ((min a.2 b.2 : ℤ) : ℝ) ≤ p.2 ∧ + p.2 ≤ ((max a.2 b.2 : ℤ) : ℝ) := by + simpa only [Int.cast_min, Int.cast_max] using hb.2 + have hx' := int_between_of_near hx.1 hx.2 ⟨hc.1, hc.2.1⟩ + have hy' := int_between_of_near hy.1 hy.2 ⟨hc.2.2.1, hc.2.2.2⟩ + exact (realPoint_mem_segment_iff hab).2 ⟨hx'.1, hx'.2, hy'.1, hy'.2⟩ + +/-- A nonincident integer vertex has a whole open unit box disjoint from a route. -/ +theorem gridPoint_mem_support_of_near {r : Route} (hr : IsOrthogonal r) + {c : Point} {p : ℝ × ℝ} (hp : p ∈ support r) (hc : nearGridPoint c p) : + realPoint c ∈ support r := by + rcases hp with ⟨a, ha, rfl⟩ | ⟨e, he, hp⟩ + · exact Or.inl ⟨a, ha, congrArg realPoint (eq_of_nearGridPoint hc).symm⟩ + · exact Or.inr ⟨e, he, gridPoint_mem_segment_of_near (hr e he).1 hp hc⟩ + +/-- In a simple route, the initial grid vertex lies on no segment other than the first. -/ +theorem segment_eq_first_of_head_mem {a b : Point} {r : Route} + (hr : IsSimple (a :: b :: r)) {e : Point × Point} + (he : e ∈ (a :: b :: r).consecutivePairs) (ha : realPoint a ∈ segment e.1 e.2) : + e = (a, b) := by + by_contra hne + have he' : e ∈ (b :: r).consecutivePairs := by + simpa only [List.consecutivePairs, List.tail_cons, List.zip_cons_cons, List.mem_cons, + hne, false_or] using he + have hfirst : (a, b) ∈ (a :: b :: r).consecutivePairs := by + simp [List.consecutivePairs] + have ha' : realPoint a ∈ segment a b := _root_.left_mem_segment ℝ _ _ + have hh := (hr.2 e he (a, b) hfirst hne (realPoint a) ha ha').1 + have hnot : a ∉ b :: r := (List.nodup_cons.mp hr.1).1 + have hepoints := List.of_mem_zip he' + rcases hh with hh | hh + · exact hnot (realPoint_injective hh ▸ hepoints.1) + · exact hnot (realPoint_injective hh ▸ List.mem_of_mem_tail hepoints.2) + +/-- The open unit box about a route's initial vertex meets only its first segment. -/ +theorem mem_first_segment_of_near_head {a b : Point} {r : Route} + (ho : IsOrthogonal (a :: b :: r)) (hs : IsSimple (a :: b :: r)) + {p : ℝ × ℝ} (hp : p ∈ support (a :: b :: r)) (ha : nearGridPoint a p) : + p ∈ segment a b := by + rcases hp with ⟨q, hq, rfl⟩ | ⟨e, he, hp⟩ + · obtain rfl := eq_of_nearGridPoint ha + exact _root_.left_mem_segment ℝ _ _ + · have hea := gridPoint_mem_segment_of_near (ho e he).1 hp ha + obtain rfl := segment_eq_first_of_head_mem hs he hea + exact hp + +/-- The tail of a simple proper orthogonal route avoids the entire open unit box about +the route's initial grid vertex. -/ +theorem not_near_head_tail {a b : Point} {r : Route} + (ho : IsOrthogonal (a :: b :: r)) (hs : IsSimple (a :: b :: r)) + {p : ℝ × ℝ} (hp : p ∈ support (b :: r)) : ¬ nearGridPoint a p := by + intro hn + have ha := gridPoint_mem_support_of_near ho.of_cons hp hn + have hab : a = b := realPoint_injective + (first_segment_tail_contact hs (_root_.left_mem_segment ℝ _ _) ha) + exact (ho (a, b) (by simp [List.consecutivePairs])).2 hab + +/-- Cutting the first segment at one of its grid points preserves proper axis alignment +provided the new segment has not collapsed. -/ +theorem axisAligned_cut_first {a b c : Point} (hab : a.1 = b.1 ∨ a.2 = b.2) + (hc : realPoint c ∈ segment a b) (hcb : c ≠ b) : AxisAligned c b := by + have hb := (realPoint_mem_segment_iff hab).1 hc + refine ⟨?_, hcb⟩ + rcases hab with hab | hab + · left + omega + · right + omega + +/-- Removing an initial portion of the first segment only shrinks a route's real support. -/ +theorem support_cut_first_subset {a b c : Point} {r : Route} + (hc : realPoint c ∈ segment a b) : support (c :: b :: r) ⊆ support (a :: b :: r) := by + have hsub : segment c b ⊆ segment a b := + (convex_segment (𝕜 := ℝ) (realPoint a) (realPoint b)).segment_subset hc + (_root_.right_mem_segment ℝ _ _) + rw [support_cons_cons, support_cons_cons] + exact Set.union_subset_union_left _ hsub + +/-- Endpoint cutting preserves a route's orthogonality. -/ +theorem isOrthogonal_cut_first {a b c : Point} {r : Route} + (hr : IsOrthogonal (a :: b :: r)) (hc : realPoint c ∈ segment a b) (hcb : c ≠ b) : + IsOrthogonal (c :: b :: r) := by + intro e he + simp only [List.consecutivePairs, List.tail_cons, List.zip_cons_cons, List.mem_cons] at he + rcases he with rfl | he + · exact axisAligned_cut_first (hr (a, b) (by simp [List.consecutivePairs])).1 hc hcb + · exact hr.of_cons e he + +/-- Moving the initial endpoint forward within its first segment preserves continuous +simplicity. No repeated point or contact with a later segment is introduced. -/ +theorem isSimple_cut_first {a b c : Point} {r : Route} + (hr : IsSimple (a :: b :: r)) (hc : realPoint c ∈ segment a b) (hcb : c ≠ b) : + IsSimple (c :: b :: r) := by + have hsub : segment c b ⊆ segment a b := + (convex_segment (𝕜 := ℝ) (realPoint a) (realPoint b)).segment_subset hc + (_root_.right_mem_segment ℝ _ _) + have hnot : c ∉ b :: r := by + intro hm + have hp : realPoint c ∈ support (b :: r) := Or.inl ⟨c, hm, rfl⟩ + exact hcb (realPoint_injective (first_segment_tail_contact hr hc hp)) + have hne (e : Point × Point) (he : e ∈ (b :: r).consecutivePairs) : (a, b) ≠ e := by + intro h + have hp := (List.of_mem_zip he).1 + have hnot' := (List.nodup_cons.mp hr.1).1 + exact hnot' (by simpa only [← h] using hp) + have hmem (e : Point × Point) (he : e ∈ (b :: r).consecutivePairs) : + e ∈ (a :: b :: r).consecutivePairs := by + simpa only [List.consecutivePairs, List.tail_cons, List.zip_cons_cons, List.mem_cons] + using Or.inr he + have hcontact (e : Point × Point) (he : e ∈ (b :: r).consecutivePairs) : + EndpointContacts c b e.1 e.2 := by + intro p hp hq + have hp' := hsub hp + have htail : p ∈ support (b :: r) := Or.inr ⟨e, he, hq⟩ + have hpb := first_segment_tail_contact hr hp' htail + exact ⟨Or.inr hpb, + (hr.2 (a, b) (by simp [List.consecutivePairs]) e (hmem e he) (hne e he) p hp' hq).2⟩ + refine ⟨List.nodup_cons.mpr ⟨hnot, hr.1.tail⟩, ?_⟩ + intro e he f hf hneq + simp only [List.consecutivePairs, List.tail_cons, List.zip_cons_cons, List.mem_cons] at he hf + rcases he with rfl | he <;> rcases hf with rfl | hf + · exact False.elim (hneq rfl) + · exact hcontact f hf + · exact (hcontact e he).symm + · exact hr.of_cons.2 e he f hf hneq + +/-- Manhattan length is additive when a grid point lies on an orthogonal segment. -/ +theorem length_add_cut_first {a b c : Point} (hab : a.1 = b.1 ∨ a.2 = b.2) + (hc : realPoint c ∈ segment a b) : length a c + length c b = length a b := by + have h := (realPoint_mem_segment_iff hab).1 hc + simp only [length] + omega + +/-- Endpoint cutting subtracts exactly the removed Manhattan length. -/ +theorem wireLength_cut_first {a b c : Point} {r : Route} + (hab : a.1 = b.1 ∨ a.2 = b.2) (hc : realPoint c ∈ segment a b) : + wireLength (c :: b :: r) + length a c = wireLength (a :: b :: r) := by + have h := length_add_cut_first hab hc + simp only [wireLength, List.consecutivePairs, List.tail_cons, List.zip_cons_cons, + List.map_cons, List.sum_cons] + omega + +end Cslib.Orthogonal diff --git a/Complexity/Foundations/Geometry/Orthogonal/Join.lean b/Complexity/Foundations/Geometry/Orthogonal/Join.lean new file mode 100644 index 000000000..14bf499fd --- /dev/null +++ b/Complexity/Foundations/Geometry/Orthogonal/Join.lean @@ -0,0 +1,116 @@ +/- +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.Isolation +import Mathlib.Data.List.Nodup + +/-! +# Gluing local and exterior incidence routes + +Two simple orthogonal routes can be joined at a common endpoint when their full real supports +intersect only there. The joined route is again simple and orthogonal; existing subdivision +lemmas identify its support, final endpoint, segment list, and Manhattan wire length. + +## Main results + +* `head_mem_segment_endpoint`, `last_mem_segment_endpoint`: an endpoint of a simple route + can occur on a segment only as a segment endpoint. +* `isOrthogonal_join`, `isSimple_join`: geometric correctness of endpoint gluing. + +## References + +These finite polygonal-route gluing lemmas and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Orthogonal + + +/-- A simple route's initial listed vertex can lie on one of its segments only as an endpoint. +-/ +theorem head_mem_segment_endpoint {r : Route} (hs : IsSimple r) {p : Point} + (hp : r.head? = some p) {e : Point × Point} (he : e ∈ r.consecutivePairs) + (hm : realPoint p ∈ segment e.1 e.2) : p = e.1 ∨ p = e.2 := by + obtain ⟨tail, rfl⟩ := List.head?_eq_some_iff.mp hp + cases tail with + | nil => simp [List.consecutivePairs] at he + | cons b tail => + have heq := segment_eq_first_of_head_mem hs he hm + exact Or.inl (congrArg (fun e : Point × Point => e.1) heq).symm + +/-- A simple route's final listed vertex can lie on one of its segments only as an endpoint. +-/ +theorem last_mem_segment_endpoint {r : Route} (hs : IsSimple r) {p : Point} + (hp : r.getLast? = some p) {e : Point × Point} (he : e ∈ r.consecutivePairs) + (hm : realPoint p ∈ segment e.1 e.2) : p = e.1 ∨ p = e.2 := by + induction r with + | nil => simp at hp + | cons a r ih => + cases r with + | nil => simp [List.consecutivePairs] at he + | cons b r => + have hp' : (b :: r).getLast? = some p := by + simpa only [List.getLast?_cons_cons] using hp + simp only [List.consecutivePairs, List.tail_cons, List.zip_cons_cons, List.mem_cons] at he + rcases he with rfl | he + · have ht : realPoint p ∈ support (b :: r) := + Or.inl ⟨p, List.mem_of_getLast? hp', rfl⟩ + exact Or.inr (realPoint_injective (first_segment_tail_contact hs hm ht)) + · exact ih hs.of_cons hp' he + +/-- Joining properly orthogonal routes at their common listed endpoint remains orthogonal. -/ +theorem isOrthogonal_join {q r : Route} {p : Point} + (hq : q.getLast? = some p) (hr : r.head? = some p) + (hoq : IsOrthogonal q) (hor : IsOrthogonal r) : IsOrthogonal (q ++ r.tail) := by + intro e he + rw [consecutivePairs_join hq hr, List.mem_append] at he + exact he.elim (hoq e) (hor e) + +/-- Joining simple routes whose only continuous intersection is their common endpoint +introduces neither repeated listed points nor forbidden segment contacts. -/ +theorem isSimple_join {q r : Route} {p : Point} + (hq : q.getLast? = some p) (hr : r.head? = some p) + (hsq : IsSimple q) (hsr : IsSimple r) + (hc : ∀ z ∈ support q, z ∈ support r → z = realPoint p) : + IsSimple (q ++ r.tail) := by + have hrnot : p ∉ r.tail := by + obtain ⟨tail, rfl⟩ := List.head?_eq_some_iff.mp hr + exact (List.nodup_cons.mp hsr.1).1 + have hnd : (q ++ r.tail).Nodup := by + refine List.nodup_append.mpr ⟨hsq.1, hsr.1.tail, ?_⟩ + intro x hx y hy heq + subst y + have hz := hc (realPoint x) (Or.inl ⟨x, hx, rfl⟩) + (Or.inl ⟨x, List.mem_of_mem_tail hy, rfl⟩) + exact hrnot (realPoint_injective hz ▸ hy) + have hcross (e : Point × Point) (he : e ∈ q.consecutivePairs) + (f : Point × Point) (hf : f ∈ r.consecutivePairs) : + EndpointContacts e.1 e.2 f.1 f.2 := by + intro z hz hz' + have hzp := hc z (Or.inr ⟨e, he, hz⟩) (Or.inr ⟨f, hf, hz'⟩) + have hep := last_mem_segment_endpoint hsq hq he (hzp ▸ hz) + have hfp := head_mem_segment_endpoint hsr hr hf (hzp ▸ hz') + constructor + · rcases hep with hep | hep + · exact Or.inl (hzp.trans (congrArg realPoint hep)) + · exact Or.inr (hzp.trans (congrArg realPoint hep)) + · rcases hfp with hfp | hfp + · exact Or.inl (hzp.trans (congrArg realPoint hfp)) + · exact Or.inr (hzp.trans (congrArg realPoint hfp)) + refine ⟨hnd, ?_⟩ + intro e he f hf hne + rw [consecutivePairs_join hq hr, List.mem_append] at he hf + rcases he with he | he <;> rcases hf with hf | hf + · exact hsq.2 e he f hf hne + · exact hcross e he f hf + · exact (hcross f hf e he).symm + · exact hsr.2 e he f hf hne + +end Cslib.Orthogonal diff --git a/Complexity/Foundations/Geometry/Orthogonal/Mirror.lean b/Complexity/Foundations/Geometry/Orthogonal/Mirror.lean new file mode 100644 index 000000000..1f4804b19 --- /dev/null +++ b/Complexity/Foundations/Geometry/Orthogonal/Mirror.lean @@ -0,0 +1,145 @@ +/- +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.Simple +import Mathlib.Analysis.Convex.Basic +import Mathlib.Data.List.Nodup +import Mathlib.Tactic.Ring + +/-! +# Horizontal reflection of orthogonal routes + +## Main definitions + +* `mirrorPoint`, `mirrorReal`, `mirrorRoute`: reflection in the vertical axis. + +## Main results + +* `support_mirrorRoute`, `isOrthogonal_mirrorRoute_iff`, `isSimple_mirrorRoute_iff`. +* `wireLength_mirrorRoute`: reflection preserves Manhattan wire length. + +## References + +These elementary affine-coordinate lemmas and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Orthogonal + + +/-- Horizontal reflection of integer points. -/ +def mirrorPoint (p : Point) : Point := (-p.1, p.2) + +/-- The corresponding horizontal reflection in the real plane. -/ +def mirrorReal (p : ℝ × ℝ) : ℝ × ℝ := (-p.1, p.2) + +/-- Reflect every listed point. -/ +def mirrorRoute (r : Route) : Route := r.map mirrorPoint + +private def mirrorLinear : (ℝ × ℝ) →ₗ[ℝ] (ℝ × ℝ) where + toFun := mirrorReal + map_add' p q := by ext <;> simp [mirrorReal, add_comm] + map_smul' s p := by ext <;> simp [mirrorReal] + +/-- Integer and real reflections commute with inclusion of the lattice. -/ +theorem realPoint_mirrorPoint (p : Point) : + realPoint (mirrorPoint p) = mirrorReal (realPoint p) := by + ext <;> simp [mirrorPoint, mirrorReal, realPoint] + +private theorem mirrorPoint_involutive : Function.Involutive mirrorPoint := by + intro p + ext <;> simp [mirrorPoint] + +private theorem mirrorReal_involutive : Function.Involutive mirrorReal := by + intro p + ext <;> simp [mirrorReal] + +/-- Reflection maps complete real segments. -/ +theorem segment_mirrorPoint (a b : Point) : + segment (mirrorPoint a) (mirrorPoint b) = mirrorReal '' segment a b := by + have h := image_segment ℝ mirrorLinear.toAffineMap (realPoint a) (realPoint b) + change mirrorReal '' _root_.segment ℝ (realPoint a) (realPoint b) = + _root_.segment ℝ (mirrorReal (realPoint a)) (mirrorReal (realPoint b)) at h + simpa only [Orthogonal.segment, realPoint_mirrorPoint] using h.symm + +/-- Reflected segment membership is reflected membership. -/ +theorem mem_segment_mirrorPoint_iff (a b : Point) (p : ℝ × ℝ) : + mirrorReal p ∈ segment (mirrorPoint a) (mirrorPoint b) ↔ p ∈ segment a b := by + rw [segment_mirrorPoint] + exact mirrorReal_involutive.injective.mem_set_image + +/-- Reflection maps every listed segment without changing its index. -/ +theorem consecutivePairs_mirrorRoute (r : Route) : + (mirrorRoute r).consecutivePairs = + r.consecutivePairs.map (fun e => (mirrorPoint e.1, mirrorPoint e.2)) := by + simp only [mirrorRoute, List.consecutivePairs, ← List.map_tail, List.zip_map] + rfl + +/-- Reflection maps the entire continuous route support. -/ +theorem support_mirrorRoute (r : Route) : support (mirrorRoute r) = mirrorReal '' support r := by + ext p + simp only [support, Set.mem_ofPred_eq, consecutivePairs_mirrorRoute] + simp only [mirrorRoute, List.mem_map, Set.mem_image] + constructor + · rintro (⟨_, ⟨a, ha, rfl⟩, rfl⟩ | ⟨_, ⟨e, he, rfl⟩, hp⟩) + · exact ⟨realPoint a, Or.inl ⟨a, ha, rfl⟩, (realPoint_mirrorPoint a).symm⟩ + · obtain ⟨q, hq, rfl⟩ := (segment_mirrorPoint e.1 e.2) ▸ hp + exact ⟨q, Or.inr ⟨e, he, hq⟩, rfl⟩ + · rintro ⟨q, ⟨a, ha, rfl⟩ | ⟨e, he, hq⟩, rfl⟩ + · exact Or.inl ⟨mirrorPoint a, ⟨a, ha, rfl⟩, (realPoint_mirrorPoint a).symm⟩ + · exact Or.inr ⟨(mirrorPoint e.1, mirrorPoint e.2), ⟨e, he, rfl⟩, + (segment_mirrorPoint e.1 e.2) ▸ Set.mem_image_of_mem _ hq⟩ + +private theorem axisAligned_mirrorPoint_iff (a b : Point) : + AxisAligned (mirrorPoint a) (mirrorPoint b) ↔ AxisAligned a b := by + simp [AxisAligned, mirrorPoint, Prod.ext_iff] + +/-- Reflection preserves proper orthogonality. -/ +theorem isOrthogonal_mirrorRoute_iff (r : Route) : + IsOrthogonal (mirrorRoute r) ↔ IsOrthogonal r := by + simp only [IsOrthogonal, consecutivePairs_mirrorRoute, List.mem_map, + forall_exists_index, and_imp, forall_apply_eq_imp_iff₂, axisAligned_mirrorPoint_iff] + +private theorem endpointContacts_mirrorPoint_iff (a b c d : Point) : + EndpointContacts (mirrorPoint a) (mirrorPoint b) (mirrorPoint c) (mirrorPoint d) ↔ + EndpointContacts a b c d := by + constructor + · intro h p hp hq + have hh := h (mirrorReal p) ((mem_segment_mirrorPoint_iff a b p).2 hp) + ((mem_segment_mirrorPoint_iff c d p).2 hq) + simp only [realPoint_mirrorPoint] at hh + exact ⟨hh.1.imp (fun h => mirrorReal_involutive.injective h) + (fun h => mirrorReal_involutive.injective h), + hh.2.imp (fun h => mirrorReal_involutive.injective h) + (fun h => mirrorReal_involutive.injective h)⟩ + · intro h p hp hq + obtain ⟨q, hqa, rfl⟩ := (segment_mirrorPoint a b) ▸ hp + have hh := h q hqa ((mem_segment_mirrorPoint_iff c d q).1 hq) + simp only [realPoint_mirrorPoint] + exact ⟨hh.1.imp (congrArg _) (congrArg _), hh.2.imp (congrArg _) (congrArg _)⟩ + +/-- Reflection preserves continuous simplicity. -/ +theorem isSimple_mirrorRoute_iff (r : Route) : IsSimple (mirrorRoute r) ↔ IsSimple r := by + simp only [IsSimple, consecutivePairs_mirrorRoute] + simp only [mirrorRoute, List.nodup_map_iff mirrorPoint_involutive.injective, List.mem_map, + forall_exists_index, and_imp, forall_apply_eq_imp_iff₂, endpointContacts_mirrorPoint_iff] + simp only [ne_eq, Prod.mk.injEq, mirrorPoint_involutive.injective.eq_iff, ← Prod.ext_iff] + +private theorem length_mirrorPoint (a b : Point) : + length (mirrorPoint a) (mirrorPoint b) = length a b := by + have hx : -b.1 - -a.1 = -(b.1 - a.1) := by ring + simp only [length, mirrorPoint, hx, Int.natAbs_neg] + +/-- Reflection preserves total Manhattan wire length. -/ +theorem wireLength_mirrorRoute (r : Route) : wireLength (mirrorRoute r) = wireLength r := by + simp only [wireLength, consecutivePairs_mirrorRoute, List.map_map, Function.comp_def, + length_mirrorPoint] + +end Cslib.Orthogonal diff --git a/Complexity/Foundations/Geometry/Orthogonal/Reverse.lean b/Complexity/Foundations/Geometry/Orthogonal/Reverse.lean new file mode 100644 index 000000000..4fb6e9ce0 --- /dev/null +++ b/Complexity/Foundations/Geometry/Orthogonal/Reverse.lean @@ -0,0 +1,117 @@ +/- +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.Simple +import Mathlib.Data.List.Nodup +import Mathlib.Data.List.Zip + +/-! +# Orientation independence of orthogonal incidence routes + +Reversing a route preserves its full real support, proper orthogonality, continuous simplicity, +Manhattan wire length, and segment count. This normalizes supplied incidence routes without +changing their drawing. + +## Main results + +* `support_reverse`, `isOrthogonal_reverse_iff`, `isSimple_reverse_iff`. +* `wireLength_reverse`, `consecutivePairs_length_reverse`. + +## References + +These finite-route reversal lemmas and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Orthogonal + + +private theorem consecutivePairs_append_singleton (r : Route) (a : Point) : + (r ++ [a]).consecutivePairs = + r.consecutivePairs ++ r.getLast?.toList.map (fun b => (b, a)) := by + induction r with + | nil => rfl + | cons b r ih => + cases r with + | nil => rfl + | cons c r => + simp only [List.cons_append, List.consecutivePairs, List.tail_cons, + List.zip_cons_cons, List.getLast?_cons_cons, List.cons_append] at ih ⊢ + exact congrArg ((b, c) :: ·) ih + +/-- Reversal reverses segment order and swaps each segment's endpoints. -/ +theorem consecutivePairs_reverse (r : Route) : + r.reverse.consecutivePairs = r.consecutivePairs.reverse.map Prod.swap := by + induction r with + | nil => rfl + | cons a r ih => + cases r with + | nil => rfl + | cons b r => + rw [List.reverse_cons, consecutivePairs_append_singleton, ih] + simp [List.consecutivePairs, List.getLast?_reverse] + +/-- Reversal preserves the number of listed segments. -/ +theorem consecutivePairs_length_reverse (r : Route) : + r.reverse.consecutivePairs.length = r.consecutivePairs.length := by + rw [consecutivePairs_reverse, List.length_map, List.length_reverse] + +/-- Reversal preserves the entire continuous support. -/ +theorem support_reverse (r : Route) : support r.reverse = support r := by + ext p + simp only [support, Set.mem_ofPred_eq, consecutivePairs_reverse, List.mem_reverse, + List.mem_map, exists_exists_and_eq_and, Prod.swap] + constructor + · rintro (h | ⟨e, he, hp⟩) + · exact Or.inl h + · refine Or.inr ⟨e, he, ?_⟩ + change p ∈ _root_.segment ℝ (realPoint e.1) (realPoint e.2) + rw [_root_.segment_symm] + exact hp + · rintro (h | ⟨e, he, hp⟩) + · exact Or.inl h + · refine Or.inr ⟨e, he, ?_⟩ + change p ∈ _root_.segment ℝ (realPoint e.2) (realPoint e.1) + rw [_root_.segment_symm] + exact hp + +private theorem axisAligned_swap_iff (a b : Point) : AxisAligned b a ↔ AxisAligned a b := by + simp [AxisAligned, eq_comm] + +/-- Reversal preserves proper orthogonality. -/ +theorem isOrthogonal_reverse_iff (r : Route) : IsOrthogonal r.reverse ↔ IsOrthogonal r := by + simp only [IsOrthogonal, consecutivePairs_reverse, List.mem_map, List.mem_reverse, + forall_exists_index, and_imp, forall_apply_eq_imp_iff₂, Prod.swap, axisAligned_swap_iff] + +private theorem isSimple_reverse {r : Route} (h : IsSimple r) : IsSimple r.reverse := by + refine ⟨by simpa only [List.nodup_reverse] using h.1, ?_⟩ + intro e he f hf hne + simp only [consecutivePairs_reverse, List.mem_map, List.mem_reverse] at he hf + obtain ⟨e, he, rfl⟩ := he + obtain ⟨f, hf, rfl⟩ := hf + have hne' : e ≠ f := fun heq => hne (congrArg Prod.swap heq) + exact (h.2 e he f hf hne').reverse_left.reverse_right + +/-- Reversal preserves continuous simplicity. -/ +theorem isSimple_reverse_iff (r : Route) : IsSimple r.reverse ↔ IsSimple r := + ⟨fun h => by simpa only [List.reverse_reverse] using isSimple_reverse h, isSimple_reverse⟩ + +private theorem length_swap (a b : Point) : length b a = length a b := by + unfold length + congr 1 + · rw [← Int.natAbs_neg (a.1 - b.1), neg_sub] + · rw [← Int.natAbs_neg (a.2 - b.2), neg_sub] + +/-- Reversal preserves total Manhattan wire length. -/ +theorem wireLength_reverse (r : Route) : wireLength r.reverse = wireLength r := by + simp only [wireLength, consecutivePairs_reverse, List.map_map, Function.comp_def, + Prod.swap, length_swap, List.map_reverse, List.sum_reverse] + +end Cslib.Orthogonal diff --git a/Complexity/Foundations/Geometry/Orthogonal/Scale.lean b/Complexity/Foundations/Geometry/Orthogonal/Scale.lean new file mode 100644 index 000000000..f83291fc3 --- /dev/null +++ b/Complexity/Foundations/Geometry/Orthogonal/Scale.lean @@ -0,0 +1,162 @@ +/- +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.Simple +import Mathlib.Analysis.Convex.Basic + +/-! +# Integer dilation of orthogonal drawings + +Positive integer dilation preserves continuous route supports, proper orthogonality, and +simplicity, multiplies Manhattan wire length, and preserves the number of listed segments. + +## Main definitions + +* `dilateRoute`: multiply every integer coordinate by a natural factor. + +## Main results + +* `support_dilateRoute`, `isOrthogonal_dilateRoute_iff`, `isSimple_dilateRoute_iff`. +* `wireLength_dilateRoute`, `consecutivePairs_length_dilateRoute`. + +## References + +These elementary affine-coordinate lemmas and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Orthogonal + + +/-- Dilate each listed integer point by the same natural factor. -/ +def dilateRoute (k : ℕ) (r : Route) : Route := r.map (k • ·) + +/-- Integer dilation commutes with inclusion into the real plane. -/ +theorem realPoint_nsmul (k : ℕ) (p : Point) : + realPoint (k • p) = (k : ℝ) • realPoint p := by + rcases p with ⟨px, py⟩ + ext <;> simp [realPoint, Prod.smul_mk] + +/-- Positive integer dilation is injective on grid points. -/ +theorem nsmul_point_injective {k : ℕ} (hk : k ≠ 0) : + Function.Injective (fun p : Point => k • p) := by + rintro ⟨ax, ay⟩ ⟨bx, by'⟩ h + have hk' : (k : ℤ) ≠ 0 := Nat.cast_ne_zero.mpr hk + simp only [Prod.smul_mk, Int.nsmul_eq_mul, Prod.mk.injEq] at h + exact Prod.ext (mul_left_cancel₀ hk' h.1) (mul_left_cancel₀ hk' h.2) + +private theorem real_nsmul_point_injective {k : ℕ} (hk : k ≠ 0) : + Function.Injective (fun p : ℝ × ℝ => (k : ℝ) • p) := by + rintro ⟨ax, ay⟩ ⟨bx, by'⟩ h + have hk' : (k : ℝ) ≠ 0 := Nat.cast_ne_zero.mpr hk + simp only [Prod.smul_mk, smul_eq_mul, Prod.mk.injEq] at h + exact Prod.ext (mul_left_cancel₀ hk' h.1) (mul_left_cancel₀ hk' h.2) + +/-- The segment between dilated endpoints is the dilation of the original real segment. -/ +theorem segment_nsmul (k : ℕ) (a b : Point) : + segment (k • a) (k • b) = (fun p : ℝ × ℝ => (k : ℝ) • p) '' segment a b := by + have h := image_segment ℝ ((k : ℝ) • AffineMap.id ℝ (ℝ × ℝ)) + (realPoint a) (realPoint b) + change (fun p : ℝ × ℝ => (k : ℝ) • p) '' _root_.segment ℝ (realPoint a) (realPoint b) = + _root_.segment ℝ ((k : ℝ) • realPoint a) ((k : ℝ) • realPoint b) at h + simpa only [Orthogonal.segment, realPoint_nsmul] using h.symm + +/-- Membership in a dilated segment is exactly dilated membership. -/ +theorem mem_segment_nsmul_iff {k : ℕ} (hk : k ≠ 0) (a b : Point) (p : ℝ × ℝ) : + (k : ℝ) • p ∈ segment (k • a) (k • b) ↔ p ∈ segment a b := by + rw [segment_nsmul] + exact (real_nsmul_point_injective hk).mem_set_image + +/-- Dilation maps the consecutive segments in their unchanged order. -/ +theorem consecutivePairs_dilateRoute (k : ℕ) (r : Route) : + (dilateRoute k r).consecutivePairs = + r.consecutivePairs.map (fun e => (k • e.1, k • e.2)) := by + simp only [dilateRoute, List.consecutivePairs, ← List.map_tail, List.zip_map] + rfl + +/-- Dilation leaves the number of listed segments unchanged. -/ +theorem consecutivePairs_length_dilateRoute (k : ℕ) (r : Route) : + (dilateRoute k r).consecutivePairs.length = r.consecutivePairs.length := by + rw [consecutivePairs_dilateRoute, List.length_map] + +/-- Dilation maps the entire real polygonal support, including singleton routes. -/ +theorem support_dilateRoute (k : ℕ) (r : Route) : + support (dilateRoute k r) = (fun p : ℝ × ℝ => (k : ℝ) • p) '' support r := by + ext p + simp only [support, Set.mem_ofPred_eq, consecutivePairs_dilateRoute] + simp only [dilateRoute, List.mem_map, Set.mem_image] + constructor + · rintro (⟨_, ⟨a, ha, rfl⟩, rfl⟩ | ⟨_, ⟨e, he, rfl⟩, hp⟩) + · exact ⟨realPoint a, Or.inl ⟨a, ha, rfl⟩, (realPoint_nsmul k a).symm⟩ + · obtain ⟨q, hq, rfl⟩ := (segment_nsmul k e.1 e.2) ▸ hp + exact ⟨q, Or.inr ⟨e, he, hq⟩, rfl⟩ + · rintro ⟨q, ⟨a, ha, rfl⟩ | ⟨e, he, hq⟩, rfl⟩ + · exact Or.inl ⟨k • a, ⟨a, ha, rfl⟩, (realPoint_nsmul k a).symm⟩ + · exact Or.inr ⟨(k • e.1, k • e.2), ⟨e, he, rfl⟩, + (segment_nsmul k e.1 e.2) ▸ Set.mem_image_of_mem _ hq⟩ + +/-- Proper axis alignment is invariant under positive integer dilation. -/ +theorem axisAligned_nsmul_iff {k : ℕ} (hk : k ≠ 0) (a b : Point) : + AxisAligned (k • a) (k • b) ↔ AxisAligned a b := by + have hk' : (k : ℤ) ≠ 0 := Nat.cast_ne_zero.mpr hk + have hm (x y : ℤ) : (k : ℤ) * x = (k : ℤ) * y ↔ x = y := + ⟨mul_left_cancel₀ hk', congrArg _⟩ + rcases a with ⟨ax, ay⟩ + rcases b with ⟨bx, by'⟩ + simp only [AxisAligned, Prod.smul_mk, Int.nsmul_eq_mul, hm, + Prod.mk.injEq, ne_eq] + +/-- Proper orthogonality is invariant under positive integer dilation. -/ +theorem isOrthogonal_dilateRoute_iff {k : ℕ} (hk : k ≠ 0) (r : Route) : + IsOrthogonal (dilateRoute k r) ↔ IsOrthogonal r := by + simp only [IsOrthogonal, consecutivePairs_dilateRoute, List.mem_map, + forall_exists_index, and_imp, forall_apply_eq_imp_iff₂, axisAligned_nsmul_iff hk] + +/-- Every Manhattan segment length is multiplied by the dilation factor. -/ +theorem length_nsmul (k : ℕ) (a b : Point) : + length (k • a) (k • b) = k * length a b := by + rcases a with ⟨ax, ay⟩ + rcases b with ⟨bx, by'⟩ + simp [length, Prod.smul_mk, ← mul_sub, Int.natAbs_mul, Nat.mul_add] + +/-- Total Manhattan wire length is multiplied by the dilation factor. -/ +theorem wireLength_dilateRoute (k : ℕ) (r : Route) : + wireLength (dilateRoute k r) = k * wireLength r := by + simp only [wireLength, consecutivePairs_dilateRoute, List.map_map, Function.comp_def, + length_nsmul, List.sum_map_mul_left] + +/-- Endpoint-only segment contacts are invariant under positive integer dilation. -/ +theorem endpointContacts_nsmul_iff {k : ℕ} (hk : k ≠ 0) (a b c d : Point) : + EndpointContacts (k • a) (k • b) (k • c) (k • d) ↔ EndpointContacts a b c d := by + constructor + · intro h p hp hq + have hh := h ((k : ℝ) • p) ((mem_segment_nsmul_iff hk a b p).2 hp) + ((mem_segment_nsmul_iff hk c d p).2 hq) + simp only [realPoint_nsmul] at hh + exact ⟨hh.1.imp (fun h => real_nsmul_point_injective hk h) + (fun h => real_nsmul_point_injective hk h), + hh.2.imp (fun h => real_nsmul_point_injective hk h) + (fun h => real_nsmul_point_injective hk h)⟩ + · intro h p hp hq + obtain ⟨q, hqa, rfl⟩ := (segment_nsmul k a b) ▸ hp + have hqc := (mem_segment_nsmul_iff hk c d q).1 hq + have hh := h q hqa hqc + simp only [realPoint_nsmul] + exact ⟨hh.1.imp (congrArg _) (congrArg _), hh.2.imp (congrArg _) (congrArg _)⟩ + +/-- Continuous simplicity is invariant under positive integer dilation. -/ +theorem isSimple_dilateRoute_iff {k : ℕ} (hk : k ≠ 0) (r : Route) : + IsSimple (dilateRoute k r) ↔ IsSimple r := by + simp only [IsSimple, consecutivePairs_dilateRoute] + simp only [dilateRoute, List.nodup_map_iff (nsmul_point_injective hk), List.mem_map, + forall_exists_index, and_imp, forall_apply_eq_imp_iff₂, endpointContacts_nsmul_iff hk] + simp only [ne_eq, Prod.mk.injEq, (nsmul_point_injective hk).eq_iff, ← Prod.ext_iff] + +end Cslib.Orthogonal diff --git a/Complexity/Foundations/Geometry/Orthogonal/Subdivision.lean b/Complexity/Foundations/Geometry/Orthogonal/Subdivision.lean index 3ff618171..b1826bf84 100644 --- a/Complexity/Foundations/Geometry/Orthogonal/Subdivision.lean +++ b/Complexity/Foundations/Geometry/Orthogonal/Subdivision.lean @@ -206,7 +206,9 @@ theorem segmentWalk_unit (a b : Point) {e : Point × Point} theorem wireLength_segmentWalk (a b : Point) : wireLength (segmentWalk a b) = length a b := wireLength_walk _ _ _ (direction_unit _ _) -private theorem consecutivePairs_join {q r : Route} {p : Point} +/-- Joining routes at a common endpoint, listing that endpoint once, concatenates their +segment lists. -/ +theorem consecutivePairs_join {q r : Route} {p : Point} (hq : q.getLast? = some p) (hr : r.head? = some p) : (q ++ r.tail).consecutivePairs = q.consecutivePairs ++ r.consecutivePairs := by induction q with @@ -229,7 +231,8 @@ private theorem consecutivePairs_join {q r : Route} {p : Point} (a, b) :: ((b :: q).consecutivePairs ++ r.consecutivePairs) rw [ih hq'] -private theorem support_join {q r : Route} {p : Point} +/-- Joining routes at their common endpoint takes the union of their full real supports. -/ +theorem support_join {q r : Route} {p : Point} (hq : q.getLast? = some p) (hr : r.head? = some p) : support (q ++ r.tail) = support q ∪ support r := by have hmem := List.mem_of_getLast? hq @@ -244,7 +247,8 @@ private theorem support_join {q r : Route} {p : Point} List.tail_cons, Set.mem_union, or_and_right, exists_or, exists_eq_left] aesop -private theorem last_join {q r : Route} {p : Point} +/-- A route joined at a common endpoint has the last endpoint of the second route. -/ +theorem last_join {q r : Route} {p : Point} (hq : q.getLast? = some p) (hr : r.head? = some p) : (q ++ r.tail).getLast? = r.getLast? := by cases r with @@ -259,7 +263,8 @@ private theorem last_join {q r : Route} {p : Point} simp [List.getLast?_append, List.getLast?_eq_some_getLast (l := c :: r) (by simp)] -private theorem wireLength_join {q r : Route} {p : Point} +/-- Joining at a common endpoint adds the two Manhattan wire lengths. -/ +theorem wireLength_join {q r : Route} {p : Point} (hq : q.getLast? = some p) (hr : r.head? = some p) : wireLength (q ++ r.tail) = wireLength q + wireLength r := by simp only [wireLength, consecutivePairs_join hq hr, List.map_append, List.sum_append] @@ -465,7 +470,9 @@ theorem walk_nodup (a d : Point) (k : ℕ) (hd : UnitVector d) : (walk a d k).No theorem segmentWalk_nodup (a b : Point) : (segmentWalk a b).Nodup := walk_nodup _ _ _ (direction_unit _ _) -private theorem mem_support_segment {r : Route} (hlen : 2 ≤ r.length) {p : ℝ × ℝ} +/-- Every supported point of a route with at least two listed points belongs to one of its +consecutive segments. In this case separate degenerate pieces for listed points are redundant. -/ +theorem mem_support_segment {r : Route} (hlen : 2 ≤ r.length) {p : ℝ × ℝ} (hp : p ∈ support r) : ∃ e ∈ r.consecutivePairs, p ∈ segment e.1 e.2 := by induction r with | nil => simp at hlen @@ -485,7 +492,9 @@ private theorem mem_support_segment {r : Route} (hlen : 2 ≤ r.length) {p : ℝ exact ⟨e, by simpa only [List.consecutivePairs, List.tail_cons, List.zip_cons_cons, List.mem_cons] using Or.inr he, hp⟩ -private theorem first_segment_tail_contact {a b : Point} {r : Route} +/-- The first segment of a continuously simple route meets its tail only at their common +listed endpoint. -/ +theorem first_segment_tail_contact {a b : Point} {r : Route} (hs : IsSimple (a :: b :: r)) {p : ℝ × ℝ} (hp : p ∈ segment a b) (hq : p ∈ support (b :: r)) : p = realPoint b := by have hnot := (List.nodup_cons.1 hs.1).1 diff --git a/Complexity/Foundations/Geometry/Orthogonal/Trim.lean b/Complexity/Foundations/Geometry/Orthogonal/Trim.lean new file mode 100644 index 000000000..3aeed40b5 --- /dev/null +++ b/Complexity/Foundations/Geometry/Orthogonal/Trim.lean @@ -0,0 +1,290 @@ +/- +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.Scale +public import Complexity.Foundations.Geometry.Orthogonal.Isolation +import Mathlib.Tactic.Positivity +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.NormNum + +/-! +# Fixed-distance cutting of dilated incidence routes + +After dilation by `k`, every original proper integer segment has length at least `k`. +Consequently a fixed `radius < k` can be removed from the initial segment of every route. +The operation preserves the original list length, proper orthogonality, and continuous +simplicity, shrinks the dilated support, and removes exactly `radius` units of wire. + +## Main definitions + +* `cutPoint`: the point at a fixed distance along a dilated first segment. +* `cutStart`: dilate a route and remove a fixed initial distance. + +## Main results + +* `isOrthogonal_cutStart`, `isSimple_cutStart`, `support_cutStart_subset`. +* `length_cutStart`, `wireLength_cutStart`. + +## References + +The integer-grid endpoint refinement and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Orthogonal + + +/-- A point at a fixed distance along a dilated segment's cardinal direction. -/ +def cutPoint (k radius : ℕ) (a b : Point) : Point := + k • a + radius • direction a b + +/-- Dilate a route and cut a fixed length from its first segment. Degenerate input lists +are merely dilated; geometric correctness is stated for proper routes of length at least two. -/ +def cutStart (k radius : ℕ) : Route → Route + | a :: b :: r => cutPoint k radius a b :: k • b :: dilateRoute k r + | r => dilateRoute k r + +/-- A proper integer segment has positive Manhattan length. -/ +theorem length_pos_of_axisAligned {a b : Point} (hab : AxisAligned a b) : + 0 < length a b := by + have hn := hab.2 + rcases a with ⟨ax, ay⟩ + rcases b with ⟨bx, by'⟩ + simp only [length, ne_eq, Prod.mk.injEq] at hn ⊢ + omega + +/-- Multiples along a cardinal ray have their expected Manhattan distances. -/ +theorem length_add_nsmul_unit (a d : Point) (n : ℕ) (hd : UnitVector d) : + length a (a + n • d) = n := by + rcases a with ⟨ax, ay⟩ + rcases hd with rfl | rfl | rfl | rfl <;> + simp [length, Prod.smul_mk] + +/-- Distinct natural distances specify distinct points along a cardinal ray. -/ +theorem nsmul_unit_injective (a d : Point) (hd : UnitVector d) : + Function.Injective (fun n : ℕ => a + n • d) := by + intro i j hij + have hi := length_add_nsmul_unit a d i hd + have hj := length_add_nsmul_unit a d j hd + change a + i • d = a + j • d at hij + rw [hij] at hi + exact hi.symm.trans hj + +private theorem dilated_endpoint {a b : Point} (hab : a.1 = b.1 ∨ a.2 = b.2) (k : ℕ) : + k • b = k • a + (k * length a b) • direction a b := by + calc + k • b = k • (a + length a b • direction a b) := + congrArg (k • ·) (add_length_direction hab).symm + _ = k • a + (k * length a b) • direction a b := by rw [smul_add, smul_smul] + +/-- A cut shorter than the dilation factor lies on every dilated proper first segment. -/ +theorem cutPoint_mem_segment {a b : Point} (hab : AxisAligned a b) + {k radius : ℕ} (hr : radius < k) : + realPoint (cutPoint k radius a b) ∈ segment (k • a) (k • b) := by + have hlen := length_pos_of_axisAligned hab + have hrad : radius < k * length a b := by nlinarith + have hm : cutPoint k radius a b ∈ walk (k • a) (direction a b) (k * length a b) := by + rw [walk_eq_map_range] + exact List.mem_map.mpr ⟨radius, List.mem_range.mpr (by omega), rfl⟩ + have hp : realPoint (cutPoint k radius a b) ∈ + support (walk (k • a) (direction a b) (k * length a b)) := Or.inl ⟨_, hm, rfl⟩ + rw [support_walk _ _ _ (direction_unit a b), ← dilated_endpoint hab.1] at hp + exact hp + +/-- A short cut never reaches the other endpoint of a proper dilated first segment. -/ +theorem cutPoint_ne_endpoint {a b : Point} (hab : AxisAligned a b) + {k radius : ℕ} (hr : radius < k) : cutPoint k radius a b ≠ k • b := by + have hlen := length_pos_of_axisAligned hab + have hrad : radius < k * length a b := by nlinarith + intro h + rw [dilated_endpoint hab.1] at h + have he := nsmul_unit_injective (k • a) (direction a b) (direction_unit a b) h + omega + +/-- Cutting never changes the number of listed route points. -/ +theorem length_cutStart (k radius : ℕ) (r : Route) : (cutStart k radius r).length = r.length := by + cases r with + | nil => rfl + | cons a r => + cases r with + | nil => rfl + | cons b r => simp [cutStart, dilateRoute] + +/-- Cutting never changes the number of listed segments. -/ +theorem consecutivePairs_length_cutStart (k radius : ℕ) (r : Route) : + (cutStart k radius r).consecutivePairs.length = r.consecutivePairs.length := by + simp only [List.consecutivePairs, List.length_zip, List.length_tail, length_cutStart] + +/-- The fixed cut preserves proper orthogonality. -/ +theorem isOrthogonal_cutStart {r : Route} (ho : IsOrthogonal r) + {k radius : ℕ} (hr : radius < k) : IsOrthogonal (cutStart k radius r) := by + have hk : k ≠ 0 := by omega + cases r with + | nil => exact (isOrthogonal_dilateRoute_iff hk []).2 ho + | cons a r => + cases r with + | nil => exact (isOrthogonal_dilateRoute_iff hk [a]).2 ho + | cons b r => + have hab := ho (a, b) (by simp [List.consecutivePairs]) + exact isOrthogonal_cut_first ((isOrthogonal_dilateRoute_iff hk _).2 ho) + (cutPoint_mem_segment hab hr) (cutPoint_ne_endpoint hab hr) + +/-- The fixed cut preserves continuous route simplicity. -/ +theorem isSimple_cutStart {r : Route} (ho : IsOrthogonal r) (hs : IsSimple r) + {k radius : ℕ} (hr : radius < k) : IsSimple (cutStart k radius r) := by + have hk : k ≠ 0 := by omega + cases r with + | nil => exact (isSimple_dilateRoute_iff hk []).2 hs + | cons a r => + cases r with + | nil => exact (isSimple_dilateRoute_iff hk [a]).2 hs + | cons b r => + have hab := ho (a, b) (by simp [List.consecutivePairs]) + exact isSimple_cut_first ((isSimple_dilateRoute_iff hk _).2 hs) + (cutPoint_mem_segment hab hr) (cutPoint_ne_endpoint hab hr) + +/-- Every point of the cut route still belongs to the original dilated route. -/ +theorem support_cutStart_subset {r : Route} (ho : IsOrthogonal r) + {k radius : ℕ} (hr : radius < k) : + support (cutStart k radius r) ⊆ support (dilateRoute k r) := by + cases r with + | nil => exact Set.Subset.rfl + | cons a r => + cases r with + | nil => exact Set.Subset.rfl + | cons b r => + have hab := ho (a, b) (by simp [List.consecutivePairs]) + exact support_cut_first_subset (cutPoint_mem_segment hab hr) + +/-- The removed initial segment has precisely the chosen radius. -/ +theorem length_cutPoint (k radius : ℕ) (a b : Point) : + length (k • a) (cutPoint k radius a b) = radius := + length_add_nsmul_unit _ _ _ (direction_unit a b) + +/-- A proper route loses exactly the chosen radius from its dilated wire length. -/ +theorem wireLength_cutStart {r : Route} (ho : IsOrthogonal r) (hlen : 2 ≤ r.length) + {k radius : ℕ} (hr : radius < k) : + wireLength (cutStart k radius r) + radius = k * wireLength r := by + cases r with + | nil => simp at hlen + | cons a r => + cases r with + | nil => simp at hlen + | cons b r => + have hab := ho (a, b) (by simp [List.consecutivePairs]) + have hm := cutPoint_mem_segment hab hr + have hk : k ≠ 0 := by omega + have ha' : (k • a).1 = (k • b).1 ∨ (k • a).2 = (k • b).2 := + ((axisAligned_nsmul_iff hk a b).2 hab).1 + have he := wireLength_cut_first (r := dilateRoute k r) ha' hm + simpa only [length_cutPoint, ← wireLength_dilateRoute, cutStart, dilateRoute, + List.map_cons] using he + +/-- A closed real square centered on an integer grid point. -/ +def inPointBox (a : Point) (radius : ℕ) (p : ℝ × ℝ) : Prop := + (a.1 : ℝ) - radius ≤ p.1 ∧ p.1 ≤ (a.1 : ℝ) + radius ∧ + (a.2 : ℝ) - radius ≤ p.2 ∧ p.2 ≤ (a.2 : ℝ) + radius + +private theorem ray_segment_box_contact {a d : Point} (hd : UnitVector d) + {radius n : ℕ} (hr : radius ≤ n) {p : ℝ × ℝ} + (hp : p ∈ segment (a + radius • d) (a + n • d)) (hb : inPointBox a radius p) : + p = realPoint (a + radius • d) := by + have hr' : (radius : ℝ) ≤ n := by exact_mod_cast hr + have hp' := mem_segment_bounds hp + rcases a with ⟨ax, ay⟩ + rcases hd with rfl | rfl | rfl | rfl + all_goals + simp only [Prod.smul_mk, Int.nsmul_eq_mul, Prod.mk_add_mk, Int.cast_add, + Int.cast_natCast, Int.cast_neg, mul_one, mul_zero, mul_neg_one, add_zero] at hp' + rcases hp' with ⟨hx, hy⟩ + rw [Set.mem_uIcc] at hx hy + rcases hx with hx | hx <;> rcases hy with hy | hy + all_goals + apply Prod.ext <;> + simp only [realPoint, Prod.smul_mk, Int.nsmul_eq_mul, Prod.mk_add_mk, + Int.cast_add, Int.cast_natCast, Int.cast_neg, mul_one, mul_zero, + mul_neg_one, add_zero] <;> + simp only [inPointBox] at hb <;> linarith + +/-- A box smaller than the integer dilation factor lies within the original open unit box +after dividing by that factor. -/ +theorem near_of_scaled_box {a : Point} {q : ℝ × ℝ} {k radius : ℕ} + (hr : radius < k) (hb : inPointBox (k • a) radius ((k : ℝ) • q)) : + nearGridPoint a q := by + have hr' : (radius : ℝ) < k := by exact_mod_cast hr + have hk : 0 < (k : ℝ) := by exact_mod_cast (Nat.zero_le radius).trans_lt hr + change ((k • a).1 : ℝ) - radius ≤ (k : ℝ) * q.1 ∧ + (k : ℝ) * q.1 ≤ ((k • a).1 : ℝ) + radius ∧ + ((k • a).2 : ℝ) - radius ≤ (k : ℝ) * q.2 ∧ + (k : ℝ) * q.2 ≤ ((k • a).2 : ℝ) + radius at hb + simp only [Prod.smul_fst, Prod.smul_snd, Int.nsmul_eq_mul, Int.cast_mul, + Int.cast_natCast] at hb + unfold nearGridPoint + constructor + · nlinarith [hb.1] + constructor + · nlinarith [hb.2.1] + constructor <;> nlinarith [hb.2.2.1, hb.2.2.2] + +/-- The trimmed exterior route meets the closed local clause box only at its designated +cut port. This controls all real points of the route. -/ +theorem cutStart_box_contact {a b : Point} {r : Route} + (ho : IsOrthogonal (a :: b :: r)) (hs : IsSimple (a :: b :: r)) + {k radius : ℕ} (hr : radius < k) {p : ℝ × ℝ} + (hp : p ∈ support (cutStart k radius (a :: b :: r))) + (hb : inPointBox (k • a) radius p) : p = realPoint (cutPoint k radius a b) := by + have hab := ho (a, b) (by simp [List.consecutivePairs]) + have hlen := length_pos_of_axisAligned hab + have hrad : radius ≤ k * length a b := by nlinarith + change p ∈ support (cutPoint k radius a b :: k • b :: dilateRoute k r) at hp + rw [support_cons_cons] at hp + rcases hp with hp | hp + · rw [dilated_endpoint hab.1] at hp + exact ray_segment_box_contact (direction_unit a b) hrad hp hb + · change p ∈ support (dilateRoute k (b :: r)) at hp + rw [support_dilateRoute] at hp + obtain ⟨q, hq, rfl⟩ := hp + exact False.elim ((not_near_head_tail ho hs hq) (near_of_scaled_box hr hb)) + +/-- Closed local boxes about distinct integer source vertices cannot meet when the +dilation factor is larger than twice their radius. -/ +theorem eq_of_inPointBoxes {a b : Point} {k radius : ℕ} (hr : 2 * radius < k) + {p : ℝ × ℝ} (ha : inPointBox (k • a) radius p) (hb : inPointBox (k • b) radius p) : + a = b := by + have hr' : 2 * (radius : ℝ) < k := by exact_mod_cast hr + rcases a with ⟨ax, ay⟩ + rcases b with ⟨bx, by'⟩ + simp only [inPointBox, Prod.smul_mk, Int.nsmul_eq_mul, Int.cast_mul, + Int.cast_natCast] at ha hb + apply Prod.ext + · by_contra h + have hd : ax + 1 ≤ bx ∨ bx + 1 ≤ ax := by omega + rcases hd with hd | hd + · have hd' : (ax : ℝ) + 1 ≤ bx := by exact_mod_cast hd + nlinarith [ha.2.1, hb.1] + · have hd' : (bx : ℝ) + 1 ≤ ax := by exact_mod_cast hd + nlinarith [hb.2.1, ha.1] + · by_contra h + have hd : ay + 1 ≤ by' ∨ by' + 1 ≤ ay := by omega + rcases hd with hd | hd + · have hd' : (ay : ℝ) + 1 ≤ by' := by exact_mod_cast hd + nlinarith [ha.2.2.2, hb.2.2.1] + · have hd' : (by' : ℝ) + 1 ≤ ay := by exact_mod_cast hd + nlinarith [hb.2.2.2, ha.2.2.1] + +/-- Distinct source vertices have disjoint closed local boxes under a sufficiently large +integer dilation. -/ +theorem pointBoxes_disjoint {a b : Point} (hab : a ≠ b) {k radius : ℕ} + (hr : 2 * radius < k) : + Disjoint {p | inPointBox (k • a) radius p} {p | inPointBox (k • b) radius p} := by + rw [Set.disjoint_left] + exact fun _ ha hb => hab (eq_of_inPointBoxes hr ha hb) + +end Cslib.Orthogonal From 86d71f6a47411c25a89c30c411e9858462d23bc8 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 21:29:35 -0400 Subject: [PATCH 3/5] feat(planar-SAT): prove parsimonious positive exact-one hardness Refs #1259; depends on #1257 and #1258. Emit the actual compact positive exact-three, occurrence-three formula and its continuous orthogonal incidence drawing, natively in polynomial time on binary labels/coordinates. Preserve guarded model counts and bound local growth by constants, with linear grid sides and quadratic clause/literal/vertex/route/segment/wire costs from ordinary 3SAT. Co-developed with Codex workers t3-96187517-positive-semantics, t3-96187517-positive-local-geometry and t3-96187517-positive-gadgets. Local technical review is not independent coordinator approval. Exact tested and reviewed source identities will be recorded in the PR. Selected headline abstracts and rendering are unaffected; the separate guide build is blocked by the existing Windows Verso linker symbol lean_md4c_markdown_parse. --- Complexity.lean | 23 + .../Problems/PlanarPositiveOneInThreeSAT.lean | 309 ++++++++ .../Problems/SAT/ExactOne/Drawing.lean | 189 +++++ .../SAT/ExactOne/DrawingAssembly.lean | 201 +++++ .../SAT/ExactOne/DrawingAvoidance.lean | 104 +++ .../SAT/ExactOne/DrawingClauseVertices.lean | 189 +++++ .../Problems/SAT/ExactOne/DrawingCosts.lean | 386 ++++++++++ .../Problems/SAT/ExactOne/DrawingCounts.lean | 90 +++ .../Problems/SAT/ExactOne/DrawingData.lean | 227 ++++++ .../SAT/ExactOne/DrawingEndpoints.lean | 132 ++++ .../SAT/ExactOne/DrawingFinitePolyTime.lean | 276 +++++++ .../Problems/SAT/ExactOne/DrawingFits.lean | 260 +++++++ .../SAT/ExactOne/DrawingPolyTime.lean | 700 ++++++++++++++++++ .../Problems/SAT/ExactOne/DrawingPorts.lean | 230 ++++++ .../SAT/ExactOne/DrawingPositionLookup.lean | 66 ++ .../SAT/ExactOne/DrawingPositions.lean | 240 ++++++ .../SAT/ExactOne/DrawingProfileBounds.lean | 91 +++ .../SAT/ExactOne/DrawingProfiles.lean | 202 +++++ .../SAT/ExactOne/DrawingRouteContacts.lean | 374 ++++++++++ .../Problems/SAT/ExactOne/DrawingRoutes.lean | 471 ++++++++++++ .../Problems/SAT/ExactOne/DrawingSource.lean | 372 ++++++++++ .../Problems/SAT/ExactOne/DrawingValid.lean | 179 +++++ .../SAT/ExactOne/DrawingVertexBoxes.lean | 211 ++++++ .../SAT/ExactOne/DrawingVertexGeometry.lean | 109 +++ .../PlanarPositiveOneInThreeSAT.lean | 60 ++ ROADMAP.md | 9 +- guide/ComplexityGuide/NPCompleteness.lean | 32 + 27 files changed, 5730 insertions(+), 2 deletions(-) create mode 100644 Complexity/Computability/Complexity/Problems/PlanarPositiveOneInThreeSAT.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/Drawing.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingAssembly.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingAvoidance.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingClauseVertices.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingCosts.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingCounts.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingData.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingEndpoints.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingFinitePolyTime.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingFits.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPolyTime.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPorts.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPositionLookup.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPositions.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingProfileBounds.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingProfiles.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingRouteContacts.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingRoutes.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingSource.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingValid.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingVertexBoxes.lean create mode 100644 Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingVertexGeometry.lean create mode 100644 ComplexityTests/PlanarPositiveOneInThreeSAT.lean diff --git a/Complexity.lean b/Complexity.lean index f8f2bcd94..c729b0b14 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -810,8 +810,31 @@ public import Complexity.Computability.Complexity.Problems.MonotoneCircuitValue. public import Complexity.Computability.Complexity.Problems.OneInThreeSAT public import Complexity.Computability.Complexity.Problems.OneInThreeSAT.Reduction public import Complexity.Computability.Complexity.Problems.Partition +public import Complexity.Computability.Complexity.Problems.PlanarPositiveOneInThreeSAT public import Complexity.Computability.Complexity.Problems.PlanarThreeSATThree public import Complexity.Computability.Complexity.Problems.SAT +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.Drawing +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingAssembly +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingAvoidance +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingClauseVertices +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingCosts +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingCounts +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingData +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingEndpoints +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingFinitePolyTime +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingFits +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingPolyTime +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingPorts +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingPositionLookup +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingPositions +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingProfileBounds +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingProfiles +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingRouteContacts +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingRoutes +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingSource +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingValid +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingVertexBoxes +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingVertexGeometry public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.Geometry public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryCollar public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryCollarData diff --git a/Complexity/Computability/Complexity/Problems/PlanarPositiveOneInThreeSAT.lean b/Complexity/Computability/Complexity/Problems/PlanarPositiveOneInThreeSAT.lean new file mode 100644 index 000000000..948283d59 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/PlanarPositiveOneInThreeSAT.lean @@ -0,0 +1,309 @@ +/- +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.PlanarThreeSATThree +public import Complexity.Computability.Complexity.Problems.OneInThreeSAT.Reduction +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingValid +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingFits +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingCosts +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingPolyTime +import Complexity.Logics.Propositional.CNF.ExactOne.PolyTime +import Mathlib.Tactic.Linarith + +/-! +# Positive exact-one SAT after parsimonious planarization + +The compiler first uses the existing parsimonious planar 3SAT-3 construction and then the +constant-factor positive exact-three replacement. The emitted integer orthogonal drawing has +the actual output incidence graph. All route contacts concern full real supports. + +Relative to a supplied planar drawing, the new replacement changes grid sides and all drawing +costs by fixed constants. Relative to the original 3SAT instance, the full composition has linear +grid sides and quadratic formula, vertex, route, segment and Manhattan wire costs. Models are +counted only on occurring variables, and every ordinary source model has a unique extension. + +## Main definitions + +* `PlanarPositiveOneInExactThreeSATThree`: the existential orthogonal-drawing language. +* `DrawnPlanarPositiveOneInExactThreeSATThree`: the supplied-drawing language. +* `toPlanarPositiveOneInExactThreeSATThree`, `toDrawnPlanarPositiveOneInExactThreeSATThree`: + the actual formula and supplied-drawing compilers. + +## Main results + +* `oneModelCount_toPlanarPositiveOneInExactThreeSATThree`: preservation of model counts. +* `PlanarPositiveOneInExactThreeSATThree_npHard`, `PlanarPositiveOneInThreeSATThree_npHard`, + `DrawnPlanarPositiveOneInExactThreeSATThree_npHard`: NP-hardness of the planar positive + variants, including the version with the actual drawing supplied. +* `computableInPolyTime_toDrawnPlanarPositiveOneInExactThreeSATThree`: native polynomial-time + computation of both the actual formula and its drawing. +* `toDrawnPlanarPositiveOneInExactThreeSATThree_fits`, + `toDrawnPlanarPositiveOneInExactThreeSATThree_costs`: explicit linear grid sides and + quadratic formula and drawing bounds. + +## References + +The local clause gadgets are from [HuntMaratheRadhakrishnanStearns1998, Theorem 3.8]. +The quantitative composition is original. +-/ + +@[expose] public section + +namespace Cslib.Complexity + +open Cslib.Logic.PL Turing.MultiTapeTM + +/-- Positive exact-three, occurrence-three exact-one formulas with an integer orthogonal +incidence drawing whose full real segments meet only at common incident endpoints. -/ +def PlanarPositiveOneInExactThreeSATThree : Set (CNF ℕ) := + {φ | φ ∈ PositiveOneInExactThreeSATThree ∧ + ∃ d, Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d} + +/-- The same language with its finite integer incidence drawing supplied as input. -/ +def DrawnPlanarPositiveOneInExactThreeSATThree : Set (CNF ℕ × Graph.OrthogonalDrawing) := + {x | x.1 ∈ PositiveOneInExactThreeSATThree ∧ + Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph x.1) x.2} + +/-- The width-at-most-three positive planar variant, without requiring ternary clauses. -/ +def PlanarPositiveOneInThreeSATThree : Set (CNF ℕ) := + {φ | φ ∈ PositiveOneInThreeSATThree ∧ + ∃ d, Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d} + +/-- Apply only the constant-factor positive compiler after the existing planarization. -/ +def toPlanarPositiveOneInExactThreeSATThree (φ : CNF ℕ) : CNF ℕ := + CNF.PositiveExactOneCompile.formula (toPlanarThreeSAT φ) + +/-- Compute the actual positive incidence drawing by replacing each planar source clause box. -/ +def toDrawnPlanarPositiveOneInExactThreeSATThree (φ : CNF ℕ) : + CNF ℕ × Graph.OrthogonalDrawing := + (toPlanarPositiveOneInExactThreeSATThree φ, + SAT.ExactOne.positiveDrawing (toPlanarThreeSAT φ) (toDrawnPlanarThreeSAT φ).2) + +/-- The positive formula is produced in native polynomial time. -/ +@[fun_prop] +theorem computableInPolyTime_toPlanarPositiveOneInExactThreeSATThree : + ComputableInPolyTime toPlanarPositiveOneInExactThreeSATThree encCNF encCNF := + (CNF.PositiveExactOneCompile.computableInPolyTime_formula encNat).comp + computableInPolyTime_toPlanarThreeSAT + +/-- Every output clause has three distinct variables. -/ +theorem exactClauses_toPlanarPositiveOneInExactThreeSATThree (φ : CNF ℕ) : + ∀ c ∈ toPlanarPositiveOneInExactThreeSATThree φ, + c.length = 3 ∧ (c.map Prod.fst).Nodup := + fun c hc => ⟨CNF.PositiveExactOneCompile.width_formula _ c hc, + CNF.PositiveExactOneCompile.nodup_formula _ c hc⟩ + +/-- There are no negative output literals. -/ +theorem positive_toPlanarPositiveOneInExactThreeSATThree (φ : CNF ℕ) : + OneSAT.positiveCheck (toPlanarPositiveOneInExactThreeSATThree φ) = true := + (OneSAT.positiveCheck_iff _).mpr (CNF.PositiveExactOneCompile.positive_formula _) + +/-- The local replacements retain the planar compiler's three-occurrence restriction. -/ +theorem count_vars_toPlanarPositiveOneInExactThreeSATThree_le (φ : CNF ℕ) : + ∀ v, (toPlanarPositiveOneInExactThreeSATThree φ).vars.count v ≤ 3 := + CNF.PositiveExactOneCompile.count_vars_formula_le _ + (width_toPlanarThreeSATThree (toThreeSATThree φ)) + (count_vars_toPlanarThreeSATThree (toThreeSATThree φ)) + +/-- Counts on occurring variables agree with the original ordinary 3SAT instance. -/ +theorem oneModelCount_toPlanarPositiveOneInExactThreeSATThree (φ : CNF ℕ) + (hw : ∀ c ∈ φ, c.length ≤ 3) : + (toPlanarPositiveOneInExactThreeSATThree φ).oneModelCount = φ.modelCount := + (CNF.PositiveExactOneCompile.oneModelCount_formula _ + (width_toPlanarThreeSATThree (toThreeSATThree φ))).trans (modelCount_toPlanarThreeSAT φ hw) + +/-- Total correctness of the formula compiler, including ordinary syntax rejection. -/ +theorem oneSatisfiable_toPlanarPositiveOneInExactThreeSATThree_iff (φ : CNF ℕ) : + (toPlanarPositiveOneInExactThreeSATThree φ).OneSatisfiable ↔ φ ∈ ThreeSAT := + (CNF.PositiveExactOneCompile.oneSatisfiable_formula _ + (width_toPlanarThreeSATThree (toThreeSATThree φ))).trans + (satisfiable_toPlanarThreeSAT_iff φ) + +/-- The new constant-factor replacements retain quadratic growth relative to original 3SAT. -/ +theorem size_toPlanarPositiveOneInExactThreeSATThree (φ : CNF ℕ) : + let s := φ.length + φ.vars.length + 1 + (toPlanarPositiveOneInExactThreeSATThree φ).length ≤ 41405000 * s ^ 2 ∧ + (toPlanarPositiveOneInExactThreeSATThree φ).vars.length ≤ 124215000 * s ^ 2 := by + have hs := (toDrawnPlanarThreeSAT_costs φ).1 + have hc := CNF.PositiveExactOneCompile.length_formula_le (toPlanarThreeSAT φ) + have hl := CNF.PositiveExactOneCompile.length_vars_formula_le (toPlanarThreeSAT φ) + dsimp only [toPlanarThreeSAT] at hc hl + dsimp only [toPlanarPositiveOneInExactThreeSATThree, toPlanarThreeSAT] + constructor <;> nlinarith + +/-- Every total output has its actual positive exact-one incidence drawing. -/ +theorem isPlanarOrthogonalDrawing_toDrawnPlanarPositiveOneInExactThreeSATThree (φ : CNF ℕ) : + Graph.IsPlanarOrthogonalDrawing + (SAT.incidenceGraph (toPlanarPositiveOneInExactThreeSATThree φ)) + (toDrawnPlanarPositiveOneInExactThreeSATThree φ).2 := + SAT.ExactOne.positiveDrawing_isPlanarOrthogonalDrawing + (isPlanarOrthogonalDrawing_toDrawnPlanarThreeSAT φ) + +/-- Membership in the strongest positive target agrees exactly with original 3SAT. -/ +theorem mem_PositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree_iff + (φ : CNF ℕ) : + toPlanarPositiveOneInExactThreeSATThree φ ∈ PositiveOneInExactThreeSATThree ↔ + φ ∈ ThreeSAT := by + change (_ ∧ _ ∧ _) ∧ _ ↔ _ + rw [and_iff_right (exactClauses_toPlanarPositiveOneInExactThreeSATThree φ), + and_iff_right (positive_toPlanarPositiveOneInExactThreeSATThree φ), + and_iff_left (count_vars_toPlanarPositiveOneInExactThreeSATThree_le φ)] + exact oneSatisfiable_toPlanarPositiveOneInExactThreeSATThree_iff φ + +/-- Correctness for the target with its actual drawing supplied. -/ +theorem mem_DrawnPlanarPositiveOneInExactThreeSATThree_toDrawn_iff (φ : CNF ℕ) : + toDrawnPlanarPositiveOneInExactThreeSATThree φ ∈ DrawnPlanarPositiveOneInExactThreeSATThree ↔ + φ ∈ ThreeSAT := by + change toPlanarPositiveOneInExactThreeSATThree φ ∈ PositiveOneInExactThreeSATThree ∧ + Graph.IsPlanarOrthogonalDrawing + (SAT.incidenceGraph (toPlanarPositiveOneInExactThreeSATThree φ)) + (toDrawnPlanarPositiveOneInExactThreeSATThree φ).2 ↔ _ + rw [and_iff_left + (isPlanarOrthogonalDrawing_toDrawnPlanarPositiveOneInExactThreeSATThree φ)] + exact mem_PositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree_iff φ + +/-- Correctness when the positive target requires the drawing's existence. -/ +theorem mem_PlanarPositiveOneInExactThreeSATThree_toPlanar_iff (φ : CNF ℕ) : + toPlanarPositiveOneInExactThreeSATThree φ ∈ PlanarPositiveOneInExactThreeSATThree ↔ + φ ∈ ThreeSAT := by + constructor + · intro h + exact + (mem_PositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree_iff φ).mp h.1 + · intro h + refine ⟨?_, + (toDrawnPlanarPositiveOneInExactThreeSATThree φ).2, + isPlanarOrthogonalDrawing_toDrawnPlanarPositiveOneInExactThreeSATThree φ⟩ + exact + (mem_PositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree_iff φ).mpr h + +/-- Positive exact-three, occurrence-three planar exact-one SAT is NP-hard. -/ +theorem PlanarPositiveOneInExactThreeSATThree_npHard : + NPHard encCNF PlanarPositiveOneInExactThreeSATThree := + ThreeSAT_npHard.trans ⟨toPlanarPositiveOneInExactThreeSATThree, + computableInPolyTime_toPlanarPositiveOneInExactThreeSATThree, + fun φ => (mem_PlanarPositiveOneInExactThreeSATThree_toPlanar_iff φ).symm⟩ + +/-- The same compiler proves the positive width-three planar variant NP-hard. -/ +theorem mem_PlanarPositiveOneInThreeSATThree_toPlanar_iff (φ : CNF ℕ) : + toPlanarPositiveOneInExactThreeSATThree φ ∈ PlanarPositiveOneInThreeSATThree ↔ + φ ∈ ThreeSAT := by + constructor + · intro h + exact (oneSatisfiable_toPlanarPositiveOneInExactThreeSATThree_iff φ).mp h.1.1.2.2 + · intro h + refine ⟨⟨⟨?_, positive_toPlanarPositiveOneInExactThreeSATThree φ, + (oneSatisfiable_toPlanarPositiveOneInExactThreeSATThree_iff φ).mpr h⟩, + count_vars_toPlanarPositiveOneInExactThreeSATThree_le φ⟩, + (toDrawnPlanarPositiveOneInExactThreeSATThree φ).2, + isPlanarOrthogonalDrawing_toDrawnPlanarPositiveOneInExactThreeSATThree φ⟩ + exact fun c hc => (exactClauses_toPlanarPositiveOneInExactThreeSATThree φ c hc).1.le + +/-- Positive width-three, occurrence-three planar exact-one SAT is NP-hard. -/ +theorem PlanarPositiveOneInThreeSATThree_npHard : + NPHard encCNF PlanarPositiveOneInThreeSATThree := + ThreeSAT_npHard.trans ⟨toPlanarPositiveOneInExactThreeSATThree, + computableInPolyTime_toPlanarPositiveOneInExactThreeSATThree, + fun φ => (mem_PlanarPositiveOneInThreeSATThree_toPlanar_iff φ).symm⟩ + +/-- The positive planar compiler preserves syntax-guarded counts on every source input. -/ +theorem sharpPositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree + (φ : CNF ℕ) : + SharpPositiveOneInExactThreeSATThree (toPlanarPositiveOneInExactThreeSATThree φ) = + SharpThreeSAT φ := by + have hg := (PositiveOneInExactThreeSATThree.syntaxCheck_iff _).mpr + ⟨exactClauses_toPlanarPositiveOneInExactThreeSATThree φ, + positive_toPlanarPositiveOneInExactThreeSATThree φ, + count_vars_toPlanarPositiveOneInExactThreeSATThree_le φ⟩ + have hplan := sharpThreeSATThree_toPlanarThreeSATThree (toThreeSATThree φ) + have hout := (ThreeSATThree.syntaxCheck_iff _).mpr + ⟨width_toPlanarThreeSATThree (toThreeSATThree φ), + count_vars_toPlanarThreeSATThree (toThreeSATThree φ)⟩ + have hsource := (ThreeSATThree.syntaxCheck_iff _).mpr + ⟨width_toThreeSATThree φ, count_vars_toThreeSATThree_le φ⟩ + simp only [SharpThreeSATThree, hout, hsource, ↓reduceIte, SharpSAT] at hplan + simp only [SharpPositiveOneInExactThreeSATThree, hg, ↓reduceIte] + change (CNF.PositiveExactOneCompile.formula + (toPlanarThreeSATThree (toThreeSATThree φ))).oneModelCount = _ + rw [CNF.PositiveExactOneCompile.oneModelCount_formula _ + (width_toPlanarThreeSATThree (toThreeSATThree φ))] + exact hplan.trans (by + simpa only [SharpThreeSATThree, hsource, ↓reduceIte, SharpSAT] using + sharpThreeSATThree_toThreeSATThree φ) + +/-- The planar formula compiler is a native parsimonious reduction of guarded counting problems. -/ +theorem parsimoniousReducible_SharpThreeSAT_planarPositiveOneInExactThreeSATThree : + ParsimoniousReducible encCNF encCNF SharpThreeSAT SharpPositiveOneInExactThreeSATThree := + ⟨toPlanarPositiveOneInExactThreeSATThree, + computableInPolyTime_toPlanarPositiveOneInExactThreeSATThree, + fun φ => (sharpPositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree φ).symm⟩ + +/-- The full compiler retains linear grid sides relative to the original ordinary 3SAT input. -/ +theorem toDrawnPlanarPositiveOneInExactThreeSATThree_fits (φ : CNF ℕ) : + let s := φ.length + φ.vars.length + 1 + Graph.drawingFits (toDrawnPlanarPositiveOneInExactThreeSATThree φ).2 + (14516280000 * s) (14516280000 * s) := by + apply Graph.drawingFits_mono + (SAT.ExactOne.positiveDrawing_fits (toPlanarThreeSAT φ) (toDrawnPlanarThreeSAT_fits φ)) <;> + omega + +/-- The actual output formula and drawing all have quadratic size relative to the original +ordinary 3SAT input. Segment and wire costs refer to the emitted full routes. -/ +theorem toDrawnPlanarPositiveOneInExactThreeSATThree_costs (φ : CNF ℕ) : + let out := toDrawnPlanarPositiveOneInExactThreeSATThree φ + let s := φ.length + φ.vars.length + 1 + out.1.length ≤ 41405000 * s ^ 2 ∧ out.1.vars.length ≤ 124215000 * s ^ 2 ∧ + out.2.1.length ≤ 165620000 * s ^ 2 ∧ out.2.2.length ≤ 124215000 * s ^ 2 ∧ + Graph.drawingSegmentCount out.2 ≤ 141463000 * s ^ 2 ∧ + Graph.drawingWireLength out.2 ≤ 31029887000000 * s ^ 2 := by + have hs := size_toPlanarPositiveOneInExactThreeSATThree φ + have hp := toDrawnPlanarThreeSAT_costs φ + have hc := SAT.ExactOne.positiveDrawing_costs + (isPlanarOrthogonalDrawing_toDrawnPlanarThreeSAT φ) + (width_toPlanarThreeSATThree (toThreeSATThree φ)) + dsimp only [toDrawnPlanarPositiveOneInExactThreeSATThree, toPlanarThreeSAT] + dsimp only [toPlanarThreeSAT] at hc + exact ⟨hs.1, hs.2, by nlinarith [hc.1, hp.1], by nlinarith [hc.2.1, hp.1], + by nlinarith [hc.2.2.1, hp.1, hp.2.2.2.1], + by nlinarith [hc.2.2.2, hp.1, hp.2.2.2.2]⟩ + +/-- Formula labels, vertex coordinates and route coordinates are all computed in native +polynomial time under their ordinary binary encodings. -/ +@[fun_prop] +theorem computableInPolyTime_toDrawnPlanarPositiveOneInExactThreeSATThree : + ComputableInPolyTime toDrawnPlanarPositiveOneInExactThreeSATThree encCNF + (encPair encCNF Graph.encOrthogonalDrawing) := by + have hf := (CNF.PositiveExactOneCompile.computableInPolyTime_formula encNat).comp + (ComputableInPolyTime.fst encCNF Graph.encOrthogonalDrawing) + have h := (hf.prod SAT.ExactOne.DrawingNative.computableInPolyTime_positiveDrawing).comp + computableInPolyTime_toDrawnPlanarThreeSAT + exact h + +/-- Ordinary 3SAT reduces natively to the positive target with its actual drawing supplied. -/ +theorem polyTimeReducible_ThreeSAT_DrawnPlanarPositiveOneInExactThreeSATThree : + PolyTimeReducible encCNF (encPair encCNF Graph.encOrthogonalDrawing) + ThreeSAT DrawnPlanarPositiveOneInExactThreeSATThree := + ⟨toDrawnPlanarPositiveOneInExactThreeSATThree, + computableInPolyTime_toDrawnPlanarPositiveOneInExactThreeSATThree, + fun φ => (mem_DrawnPlanarPositiveOneInExactThreeSATThree_toDrawn_iff φ).symm⟩ + +/-- Supplied-drawing planar positive exact-three, occurrence-three exact-one SAT is NP-hard. -/ +theorem DrawnPlanarPositiveOneInExactThreeSATThree_npHard : + NPHard (encPair encCNF Graph.encOrthogonalDrawing) DrawnPlanarPositiveOneInExactThreeSATThree := + ThreeSAT_npHard.trans polyTimeReducible_ThreeSAT_DrawnPlanarPositiveOneInExactThreeSATThree + +/-- Supplying the emitted drawing preserves the same exact guarded model counts. -/ +theorem parsimoniousReducible_SharpThreeSAT_drawnPlanarPositiveOneInExactThreeSATThree : + ParsimoniousReducible encCNF (encPair encCNF Graph.encOrthogonalDrawing) SharpThreeSAT + (fun x => SharpPositiveOneInExactThreeSATThree x.1) := + ⟨toDrawnPlanarPositiveOneInExactThreeSATThree, + computableInPolyTime_toDrawnPlanarPositiveOneInExactThreeSATThree, + fun φ => (sharpPositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree φ).symm⟩ + +end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/Drawing.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/Drawing.lean new file mode 100644 index 000000000..2377c39ba --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/Drawing.lean @@ -0,0 +1,189 @@ +/- +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.ExactOne.DrawingData +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingPorts +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingProfiles +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesPrefixData +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryCollarGlue +import Mathlib.Data.List.GetD + +/-! +# The actual positive exact-one drawing compiler + +Every source clause is replaced by its positive exact-one profile with a collar matching the +source exit directions. Source variable vertices remain shared at scaled positions. Private +source placeholders are removed, and their incidences join the trimmed source routes. + +## Main definitions + +* `positiveDrawing`: finite drawing data for the actual positive exact-one output formula. +* `positivePlacedClauses`: clause records in the actual compiler's list order. + +## Main results + +* `positivePlacedClauses_formula`: exact agreement with the actual logical formula. + +## References + +The finite-profile assembly and its explicit placement are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- The common radius of all collared positive clause replacements. -/ +def positiveRadius : ℕ := 270000 + +/-- Dilation separates all closed clause boxes. -/ +def positiveScale : ℕ := 1080000 + +/-- Translate away from the source coordinate axes. -/ +def positiveTranslation : Point := (positiveRadius, positiveRadius) + +/-- The collared local geometry selected for one original clause. -/ +def clauseGeometry (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) (i : ℕ) (c : Clause ℕ) : + LocalGeometry := + collaredPrefixGeometry (profilePrefixGeometry (clauseProfile c)) (clausePorts φ d i) + +/-- The lower-left corner of one replacement box. -/ +def positiveBoxOrigin (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) (i : ℕ) : Point := + positiveScale • Graph.drawingPosition (SAT.incidenceGraph φ) d (φ.distinctVars.length + i) + +/-- The inherited route outside its own local replacement box. -/ +def positiveExterior (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) (i j : ℕ) : Route := + translate positiveTranslation + (exteriorRoute positiveScale positiveRadius (SAT.incidenceGraph φ) d (sourceEdgeIndex φ i j)) + +/-- A source prefix joins its inherited route; an auxiliary prefix already ends at its variable. -/ +def positiveIncidenceRoute (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) (i : ℕ) + (c : Clause ℕ) (e : LocalIncidence) : Route := + let stem := translate (positiveBoxOrigin φ d i) e.2.2 + match (profileVariables (clauseProfile c)).getD e.2.1 (.inl (.inl 0)) with + | .inl (.inl j) => stem ++ (positiveExterior φ d i j).tail + | _ => stem + +/-- An actual clause, its position, and its literal routes in literal order. -/ +abbrev PositivePlacedClause := Clause (CNF.PositiveExactOneCompile.Variable ℕ) × Point × List Route + +/-- One original clause's output, using its actual global auxiliary offsets. -/ +def positiveClauseBlock (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) (i : ℕ) (c : Clause ℕ) : + List PositivePlacedClause := + (profileFormula (clauseProfile c)).zipIdx.map fun a => + (a.1.relabel (profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c), + positiveBoxOrigin φ d i + (clauseGeometry φ d i c).clausePosition a.2, + ((clauseGeometry φ d i c).incidenceRoutes.filter fun e => e.1 == a.2).map + (positiveIncidenceRoute φ d i c)) + +/-- Actual output clause records in source-block and local-clause order. -/ +def positivePlacedClauses (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + List PositivePlacedClause := + φ.zipIdx.flatMap fun c => positiveClauseBlock φ d c.2 c.1 + +/-- Fresh-variable positions in one clause box; source placeholders are omitted. -/ +def positiveAuxiliaryBlock (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) (i : ℕ) (c : Clause ℕ) : + List (CNF.PositiveExactOneCompile.Variable ℕ × Point) := + (profileVariables (clauseProfile c)).zipIdx.filterMap fun v => + match v.1 with + | .inl (.inl _) => none + | _ => some + (profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c v.1, + positiveBoxOrigin φ d i + (clauseGeometry φ d i c).variablePosition v.2) + +/-- The finite position table for actual output auxiliaries. -/ +def positiveAuxiliaryPositions (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + List (CNF.PositiveExactOneCompile.Variable ℕ × Point) := + φ.zipIdx.flatMap fun c => positiveAuxiliaryBlock φ d c.2 c.1 + +/-- Shared original-variable vertices are listed before the fresh-variable position records. -/ +def positiveSourcePositions (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + List (CNF.PositiveExactOneCompile.Variable ℕ × Point) := + φ.distinctVars.map fun v => (.inl (.inl v), positiveScale • + Graph.drawingPosition (SAT.incidenceGraph φ) d (φ.distinctVars.idxOf v) + positiveTranslation) + +/-- The position table keeps both shared source vertices and fresh clause-box vertices. -/ +def positivePositionTable (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + List (CNF.PositiveExactOneCompile.Variable ℕ × Point) := + positiveSourcePositions φ d ++ positiveAuxiliaryPositions φ d + +/-- Read the position of an actual output variable from the finite position table. -/ +def positiveVariablePoint (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (v : CNF.PositiveExactOneCompile.Variable ℕ) : Point := + ((positivePositionTable φ d).map Prod.snd).getD + (((positivePositionTable φ d).map Prod.fst).idxOf v) (0, 0) + +/-- The actual compact output drawing omits all private source-placeholder vertices. -/ +def positiveDrawing (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : Graph.OrthogonalDrawing := + (((CNF.PositiveExactOneCompile.rawFormula φ).distinctVars.map (positiveVariablePoint φ d)) ++ + ((positivePlacedClauses φ d).map fun c => c.2.1), + (positivePlacedClauses φ d).flatMap fun c => c.2.2) + +private theorem source_table_lookup (vs : List ℕ) + (tail : List (CNF.PositiveExactOneCompile.Variable ℕ × Point)) + (point : ℕ → Point) {v : ℕ} (hv : v ∈ vs) : + (((vs.map fun w => (Sum.inl (Sum.inl w), point w)) ++ tail).map Prod.snd).getD + ((((vs.map fun w => (Sum.inl (Sum.inl w), point w)) ++ tail).map Prod.fst).idxOf + (Sum.inl (Sum.inl v))) (0, 0) = point v := by + induction vs with + | nil => simp at hv + | cons w vs ih => + by_cases he : w = v + · subst w + simp [List.getD] + · have hk : (Sum.inl (Sum.inl w) : CNF.PositiveExactOneCompile.Variable ℕ) ≠ + Sum.inl (Sum.inl v) := by simpa using he + simp only [List.map_cons, List.cons_append, List.idxOf_cons_ne _ hk] + simpa only [List.getD_cons_succ] using ih (List.mem_of_ne_of_mem (Ne.symm he) hv) + +/-- A used source variable resolves to its common original scaled vertex. -/ +theorem positiveVariablePoint_source (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) {v : ℕ} + (hv : v ∈ φ.vars) : + positiveVariablePoint φ d (.inl (.inl v)) = positiveScale • + Graph.drawingPosition (SAT.incidenceGraph φ) d (φ.distinctVars.idxOf v) + + positiveTranslation := by + unfold positiveVariablePoint positivePositionTable positiveSourcePositions + exact source_table_lookup _ _ _ (CNF.mem_distinctVars.mpr hv) + +private theorem table_lookup_of_mem + (table : List (CNF.PositiveExactOneCompile.Variable ℕ × Point)) + (hnd : (table.map Prod.fst).Nodup) {v : CNF.PositiveExactOneCompile.Variable ℕ} {p : Point} + (hm : (v, p) ∈ table) : + (table.map Prod.snd).getD ((table.map Prod.fst).idxOf v) (0, 0) = p := by + obtain ⟨i, hi, he⟩ := List.mem_iff_getElem.mp hm + have hi' : i < (table.map Prod.fst).length := by simpa only [List.length_map] using hi + have hidx := hnd.idxOf_getElem i hi' + rw [List.getElem_map, he] at hidx + rw [hidx, List.getD_eq_getElem _ _ (by simpa using hi), List.getElem_map, he] + +/-- Any uniquely indexed position-table entry resolves to its prescribed actual point. -/ +theorem positiveVariablePoint_of_mem (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (hnd : ((positivePositionTable φ d).map Prod.fst).Nodup) + {v : CNF.PositiveExactOneCompile.Variable ℕ} {p : Point} + (hm : (v, p) ∈ positivePositionTable φ d) : positiveVariablePoint φ d v = p := + table_lookup_of_mem _ hnd hm + +/-- Geometric clause records retain the actual formula, variable names, and clause order. -/ +theorem positivePlacedClauses_formula (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + (positivePlacedClauses φ d).map Prod.fst = CNF.PositiveExactOneCompile.rawFormula φ := by + rw [rawFormula_eq_profiles] + simp only [positivePlacedClauses, List.map_flatMap] + simp only [List.flatMap_def] + apply congrArg List.flatten + apply List.map_congr_left + intro c _ + simp only [positiveClauseBlock, List.map_map, Function.comp_def, CNF.relabel] + have he := congrArg (List.map (Clause.relabel + (profileVariableMap c.2 (CNF.PositiveExactOneCompile.signedOffset φ c.2) c.1))) + (List.zipIdx_map_fst 0 (profileFormula (clauseProfile c.1))) + simpa only [List.map_map, Function.comp_def] using he + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingAssembly.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingAssembly.lean new file mode 100644 index 000000000..d08e15aba --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingAssembly.lean @@ -0,0 +1,201 @@ +/- +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.ExactOne.DrawingData +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingCounts +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingClauseVertices +import Mathlib.Data.List.GetD + +/-! +# Actual occurrence indices of the positive drawing + +The local clause records are assembled in the output formula's order. The occurrence-indexed +view in this module retains precisely the emitted position and route lists. + +## Main definitions + +* `positiveFormulaDrawing`: the actual compiler indexed by actual output occurrences. + +## Main results + +* `positiveFormulaDrawing_drawing`: the indexed view equals the emitted drawing. + +## References + +The assembly and indexing proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- Index the emitted route lists by actual output clause and literal positions. -/ +def positiveFormulaDrawing (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + FormulaDrawing (CNF.PositiveExactOneCompile.rawFormula φ) where + variablePoint := positiveVariablePoint φ d + clausePoints := (positivePlacedClauses φ d).map fun pc => pc.2.1 + incidenceRoute := fun o => + ((positivePlacedClauses φ d).getD o.1 ([], (0, 0), [])).2.2.getD o.2.1 [] + +private theorem map_getD_zipIdx {A B : Type*} (xs : List A) (ys : List B) (fallback : B) + (hlen : ys.length = xs.length) : + (xs.zipIdx.map fun a => ys.getD a.2 fallback) = ys := by + apply List.ext_getElem + · simpa using hlen.symm + · intro i hi hj + simp only [List.getElem_map, List.getElem_zipIdx, Nat.zero_add] + rw [List.getD_eq_getElem _ _ hj] + +private theorem placed_occurrence_routes (pcs : List PositivePlacedClause) + (hc : ∀ pc ∈ pcs, pc.2.2.length = pc.1.length) : + ((Cslib.Logic.PL.occurrences (pcs.map Prod.fst)).map fun o => + (pcs.getD o.1 ([], (0, 0), [])).2.2.getD o.2.1 []) = pcs.flatMap fun pc => pc.2.2 := by + rw [Cslib.Logic.PL.occurrences, List.zipIdx_map] + simp only [List.flatMap_map, List.map_flatMap, List.map_map, Function.comp_def] + have he : pcs.flatMap (fun pc => pc.2.2) = + pcs.zipIdx.flatMap (fun pc => pc.1.2.2) := by + have h := congrArg (List.flatMap (fun pc : PositivePlacedClause => pc.2.2)) + (List.zipIdx_map_fst (l := pcs) (i := 0)) + simpa only [List.flatMap_map, Function.comp_def] using h.symm + rw [he] + simp only [List.flatMap_def] + apply congrArg List.flatten + apply List.map_congr_left + rintro ⟨pc, i⟩ hi + have hi' := List.mem_zipIdx_iff_getElem?.mp hi + have hg : pcs.getD i ([], (0, 0), []) = pc := by + rw [List.getD_eq_getElem?_getD, hi'] + rfl + change pc.1.zipIdx.map (fun x => (pcs.getD i ([], (0, 0), [])).2.2.getD x.2 []) = _ + rw [hg] + exact map_getD_zipIdx pc.1 pc.2.2 [] (hc pc (List.mem_of_getElem? hi')) + +/-- The occurrence-indexed view has exactly the compiler's actual position and route lists. -/ +theorem positiveFormulaDrawing_drawing (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + (positiveFormulaDrawing φ d).drawing = positiveDrawing φ d := by + apply Prod.ext + · rfl + · change ((Cslib.Logic.PL.occurrences (CNF.PositiveExactOneCompile.rawFormula φ)).map + fun o => ((positivePlacedClauses φ d).getD o.1 ([], (0, 0), [])).2.2.getD o.2.1 []) = _ + rw [← positivePlacedClauses_formula φ d] + exact placed_occurrence_routes _ (fun pc hpc => positivePlacedClauses_route_count φ d hpc) + +/-- Each actual output occurrence selects its literal and route in the same placed clause. -/ +theorem positiveFormulaDrawing_occurrence (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + {o : Occurrence (CNF.PositiveExactOneCompile.Variable ℕ)} + (ho : o ∈ Cslib.Logic.PL.occurrences (CNF.PositiveExactOneCompile.rawFormula φ)) : + ∃ pc ∈ positivePlacedClauses φ d, + pc.2.1 = (positiveFormulaDrawing φ d).clausePoints.getD o.1 (0, 0) ∧ + (o.2.2, (positiveFormulaDrawing φ d).incidenceRoute o) ∈ pc.1.zip pc.2.2 := by + obtain ⟨hi, hj, hl⟩ := Cslib.Logic.PL.mem_occurrences.mp ho + have hip : o.1 < (positivePlacedClauses φ d).length := by + have h := congrArg List.length (positivePlacedClauses_formula φ d) + simp only [List.length_map] at h + omega + let pc := (positivePlacedClauses φ d)[o.1] + have hpc : pc ∈ positivePlacedClauses φ d := List.getElem_mem hip + have hec : pc.1 = (CNF.PositiveExactOneCompile.rawFormula φ)[o.1] := by + have h := congrArg (fun cs => cs.getD o.1 []) (positivePlacedClauses_formula φ d) + simpa only [List.getD_eq_getElem _ _ (show o.1 < + ((positivePlacedClauses φ d).map Prod.fst).length by simpa using hip), List.getElem_map, + List.getD_eq_getElem _ _ hi] using h + have hjp : o.2.1 < pc.1.length := by simpa only [hec] using hj + have hjr : o.2.1 < pc.2.2.length := by rw [positivePlacedClauses_route_count φ d hpc]; exact hjp + refine ⟨pc, hpc, ?_, ?_⟩ + · change pc.2.1 = ((positivePlacedClauses φ d).map (fun pc => pc.2.1)).getD o.1 (0, 0) + rw [List.getD_eq_getElem _ _ (by simpa using hip), List.getElem_map] + · have hr : (positiveFormulaDrawing φ d).incidenceRoute o = pc.2.2[o.2.1] := by + change ((positivePlacedClauses φ d).getD o.1 ([], (0, 0), [])).2.2.getD o.2.1 [] = _ + rw [List.getD_eq_getElem _ _ hip, List.getD_eq_getElem _ _ hjr] + have hli : o.2.2 = pc.1[o.2.1] := by + have h := congrArg (fun cs => cs.getD o.2.1 (.inl (.inl 0), true)) hec + simpa only [List.getD_eq_getElem _ _ hjp, List.getD_eq_getElem _ _ hj, hl] using h.symm + rw [hr, hli] + have hz := List.getElem_mem (l := pc.1.zip pc.2.2) (n := o.2.1) + (by simp only [List.length_zip]; omega) + rw [List.getElem_zip] at hz + exact hz + +/-- Geometry transformations retain the literal-to-variable index of every profile incidence. -/ +theorem clauseGeometry_literal_indices (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) (j : Fin 22) + {l : Literal (CNF.PositiveExactOneCompile.Variable ℕ)} {e : LocalIncidence} + (he : (l, e) ∈ ((profileFormula (clauseProfile c)).getD j.val []).zip + ((clauseGeometry φ d i c).incidenceRoutes.filter fun e => e.1 == j.val)) : + e.2.1 = (profileVariables (clauseProfile c)).idxOf l.1 := by + simp only [clauseGeometry, collaredPrefixGeometry, profilePrefixGeometry, + LocalGeometry.toPrefix, List.filter_map, List.map_map, Function.comp_def, + List.zip_map_right] at he + obtain ⟨⟨lit, old⟩, hold, hle⟩ := List.mem_map.mp he + have hidx := profileGeometry_literal_indices (clauseProfile c) j hold + have hli : lit = l := congrArg Prod.fst hle + have hei : old.2.1 = e.2.1 := congrArg (fun a => a.2.2.1) hle + exact hei.symm.trans (hli ▸ hidx) + +/-- A paired actual literal and route comes from its certified local incidence record. -/ +theorem positiveClauseBlock_incidence (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) {pc : PositivePlacedClause} + (hpc : pc ∈ positiveClauseBlock φ d i c) + {l : Literal (CNF.PositiveExactOneCompile.Variable ℕ)} {r : Route} + (hlr : (l, r) ∈ pc.1.zip pc.2.2) : + ∃ e ∈ (clauseGeometry φ d i c).incidenceRoutes, + r = positiveIncidenceRoute φ d i c e ∧ + pc.2.1 = positiveBoxOrigin φ d i + (clauseGeometry φ d i c).clausePosition e.1 ∧ + l.1 = profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c + ((profileVariables (clauseProfile c)).getD e.2.1 (.inl (.inl 0))) := by + obtain ⟨⟨cl, j⟩, hj, rfl⟩ := List.mem_map.mp hpc + change (l, r) ∈ (cl.relabel _).zip (List.map _ _) at hlr + simp only [Clause.relabel, List.zip_map_left, List.zip_map_right, List.map_map, + Function.comp_def] at hlr + obtain ⟨⟨lit, e⟩, hle, hnames⟩ := List.mem_map.mp hlr + have hparts := List.of_mem_zip hle + have he := List.mem_filter.mp hparts.2 + have hei : e.1 = j := beq_iff_eq.mp he.2 + have hl : l.1 = profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c lit.1 := + (congrArg (fun a => a.1.1) hnames).symm + have hr : r = positiveIncidenceRoute φ d i c e := (congrArg Prod.snd hnames).symm + have hget := List.mem_zipIdx_iff_getElem?.mp hj + have hjlt := (List.getElem?_eq_some_iff.mp hget).choose + have hbound := profileFormula_length_le (clauseProfile c) + have hecl : (profileFormula (clauseProfile c)).getD j [] = cl := by + rw [List.getD_eq_getElem?_getD, hget] + rfl + have hidx := clauseGeometry_literal_indices φ d i c ⟨j, by omega⟩ + (hecl ▸ hle) + have hlvar : lit.1 ∈ profileVariables (clauseProfile c) := by + apply CNF.mem_distinctVars.mpr + exact CNF.mem_vars.mpr ⟨cl, List.mem_of_getElem? hget, lit.2, hparts.1⟩ + refine ⟨e, he.1, hr, ?_, ?_⟩ + · simp only [hei] + · rw [hidx, List.getD_eq_getElem _ _ (List.idxOf_lt_length_of_mem hlvar), + List.getElem_idxOf (List.idxOf_lt_length_of_mem hlvar), hl] + +/-- Every actual output occurrence has a source-clause owner and a certified local route. -/ +theorem positiveFormulaDrawing_incidence (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + {o : Occurrence (CNF.PositiveExactOneCompile.Variable ℕ)} + (ho : o ∈ Cslib.Logic.PL.occurrences (CNF.PositiveExactOneCompile.rawFormula φ)) : + ∃ (i : ℕ) (hi : i < φ.length) (e : LocalIncidence), + e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes ∧ + (positiveFormulaDrawing φ d).incidenceRoute o = positiveIncidenceRoute φ d i φ[i] e ∧ + (positiveFormulaDrawing φ d).clausePoints.getD o.1 (0, 0) = + positiveBoxOrigin φ d i + (clauseGeometry φ d i φ[i]).clausePosition e.1 ∧ + o.2.2.1 = profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) φ[i] + ((profileVariables (clauseProfile φ[i])).getD e.2.1 (.inl (.inl 0))) := by + obtain ⟨pc, hpc, hp, hlr⟩ := positiveFormulaDrawing_occurrence φ d ho + obtain ⟨⟨c, i⟩, hci, hpc⟩ := List.mem_flatMap.mp hpc + obtain ⟨hi, hc⟩ := List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hci) + change i < φ.length at hi + change φ[i] = c at hc + subst c + obtain ⟨e, he, hr, hpos, hvar⟩ := positiveClauseBlock_incidence φ d i φ[i] hpc hlr + exact ⟨i, hi, e, he, hr, hp.symm.trans hpos, hvar⟩ + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingAvoidance.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingAvoidance.lean new file mode 100644 index 000000000..cfcf3bc88 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingAvoidance.lean @@ -0,0 +1,104 @@ +/- +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.ExactOne.DrawingData +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Nodup + +/-! +# Nonincident-vertex avoidance from actual occurrence contacts + +Once every displayed variable and nonempty clause has its own incident route, pairwise contact +control already implies nonincident-vertex avoidance. The only possible ambiguity would identify +a variable point with a clause point; the distinct displayed positions exclude that ambiguity. + +## Main results + +* `FormulaDrawing.avoidsVariables_of_contacts`: used variable vertices cannot lie on other routes. +* `FormulaDrawing.avoidsClauses_of_contacts`: nonempty clause vertices cannot lie on other routes. + +## References + +These elementary consequences of occurrence-indexed drawing certificates are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne.FormulaDrawing + +open Cslib.Logic.PL Orthogonal + +variable {V : Type} [DecidableEq V] {φ : CNF V} {a : FormulaDrawing φ} + +private theorem variablePoint_ne_clausePoint (hcount : a.clausePoints.length = φ.length) + (hnd : a.positions.Nodup) {v : V} (hv : v ∈ φ.vars) {i : ℕ} (hi : i < φ.length) : + a.variablePoint v ≠ a.clausePoints.getD i (0, 0) := by + have hm : a.variablePoint v ∈ φ.distinctVars.map a.variablePoint := + List.mem_map_of_mem (CNF.mem_distinctVars.mpr hv) + have hc : a.clausePoints.getD i (0, 0) ∈ a.clausePoints := by + rw [List.getD_eq_getElem _ _ (by omega)] + exact List.getElem_mem (by omega) + exact (List.nodup_append.mp hnd).2.2 _ hm _ hc + +/-- Used variable vertices cannot lie on another incidence once actual endpoint contacts and +variable/clause separation have been checked. -/ +theorem avoidsVariables_of_contacts (hcount : a.clausePoints.length = φ.length) + (hnd : a.positions.Nodup) + (hends : ∀ o ∈ Cslib.Logic.PL.occurrences φ, + (a.incidenceRoute o).getLast? = some (a.variablePoint o.2.2.1)) + (hcontacts : ∀ o ∈ Cslib.Logic.PL.occurrences φ, + ∀ t ∈ Cslib.Logic.PL.occurrences φ, o ≠ t → + ∀ p ∈ support (a.incidenceRoute o), p ∈ support (a.incidenceRoute t) → + (o.2.2.1 = t.2.2.1 ∧ p = realPoint (a.variablePoint o.2.2.1)) ∨ + (o.1 = t.1 ∧ p = realPoint (a.clausePoints.getD o.1 (0, 0)))) : + ∀ o ∈ Cslib.Logic.PL.occurrences φ, ∀ v ∈ φ.vars, + realPoint (a.variablePoint v) ∈ support (a.incidenceRoute o) → v = o.2.2.1 := by + intro o ho v hv hp + have hv' := hv + rw [← Cslib.Logic.PL.map_occurrences] at hv' + obtain ⟨t, ht, htv⟩ := List.mem_map.mp hv' + by_cases hot : o = t + · subst t + exact htv.symm + have hpt : realPoint (a.variablePoint v) ∈ support (a.incidenceRoute t) := by + rw [← htv] + exact Or.inl ⟨_, List.mem_of_getLast? (hends t ht), rfl⟩ + rcases hcontacts o ho t ht hot _ hp hpt with hc | hc + · exact htv.symm.trans hc.1.symm + · exact False.elim (variablePoint_ne_clausePoint hcount hnd hv + (Cslib.Logic.PL.mem_occurrences.mp ho).choose (realPoint_injective hc.2)) + +/-- Every nonempty clause vertex avoids other incidence routes once actual endpoint contacts +and variable/clause separation have been checked. -/ +theorem avoidsClauses_of_contacts (hcount : a.clausePoints.length = φ.length) + (hnd : a.positions.Nodup) (hnonempty : ∀ c ∈ φ, c ≠ []) + (hends : ∀ o ∈ Cslib.Logic.PL.occurrences φ, + (a.incidenceRoute o).head? = some (a.clausePoints.getD o.1 (0, 0))) + (hcontacts : ∀ o ∈ Cslib.Logic.PL.occurrences φ, + ∀ t ∈ Cslib.Logic.PL.occurrences φ, o ≠ t → + ∀ p ∈ support (a.incidenceRoute o), p ∈ support (a.incidenceRoute t) → + (o.2.2.1 = t.2.2.1 ∧ p = realPoint (a.variablePoint o.2.2.1)) ∨ + (o.1 = t.1 ∧ p = realPoint (a.clausePoints.getD o.1 (0, 0)))) : + ∀ o ∈ Cslib.Logic.PL.occurrences φ, ∀ i < φ.length, + realPoint (a.clausePoints.getD i (0, 0)) ∈ support (a.incidenceRoute o) → i = o.1 := by + intro o ho i hi hp + have hlen : 0 < φ[i].length := List.length_pos_iff.mpr (hnonempty _ (List.getElem_mem hi)) + let t : Occurrence V := (i, 0, φ[i][0]) + have ht : t ∈ Cslib.Logic.PL.occurrences φ := + Cslib.Logic.PL.mem_occurrences.mpr ⟨hi, hlen, rfl⟩ + by_cases hot : o = t + · exact (congrArg Prod.fst hot).symm + have hpt : realPoint (a.clausePoints.getD i (0, 0)) ∈ support (a.incidenceRoute t) := + Or.inl ⟨_, List.mem_of_head? (hends t ht), rfl⟩ + rcases hcontacts o ho t ht hot _ hp hpt with hc | hc + · exact False.elim (variablePoint_ne_clausePoint hcount hnd + (Cslib.Logic.PL.variable_mem_of_mem_occurrences ho) hi (realPoint_injective hc.2).symm) + · exact hc.1.symm + +end Cslib.Complexity.SAT.ExactOne.FormulaDrawing diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingClauseVertices.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingClauseVertices.lean new file mode 100644 index 000000000..01f826ae2 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingClauseVertices.lean @@ -0,0 +1,189 @@ +/- +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.ExactOne.DrawingVertexBoxes +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Nodup + +/-! +# Distinct actual clause and variable vertices + +The actual clause-position list follows the output formula. Certified inner profile vertices +are distinct, different boxes have different original clause owners, and original variable +centers cannot lie in any clause box. + +## Main results + +* `positiveClausePositions_nodup`: actual clause vertices have no repeated position. +* `positiveVariablePoint_ne_clause`: actual variables avoid every clause vertex. +* `positiveDrawing_positions_nodup`: all actual output graph vertices are distinct. + +## References + +These global vertex-separation proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- The selected clause-position list has the exact actual profile clause length. -/ +theorem clauseGeometry_clause_length (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) : + (clauseGeometry φ d i c).clausePositions.length = + (profileFormula (clauseProfile c)).length := by + simpa only [clauseGeometry, collaredPrefixGeometry, profilePrefixGeometry, + LocalGeometry.toPrefix, List.length_map] using profileGeometry_clause_length (clauseProfile c) + +private theorem map_getD_zipIdx {A B : Type*} (xs : List A) (ys : List B) (fallback : B) + (hlen : ys.length = xs.length) : + (xs.zipIdx.map fun a => ys.getD a.2 fallback) = ys := by + apply List.ext_getElem + · simpa using hlen.symm + · intro i hi hj + simp only [List.getElem_map, List.getElem_zipIdx, Nat.zero_add] + rw [List.getD_eq_getElem _ _ hj] + +/-- A clause block lists precisely the translated local clause positions in order. -/ +theorem positiveClauseBlock_positions (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) : + ((positiveClauseBlock φ d i c).map fun pc => pc.2.1) = + (clauseGeometry φ d i c).clausePositions.map (positiveBoxOrigin φ d i + ·) := by + simp only [positiveClauseBlock, List.map_map, Function.comp_def] + have he := congrArg (List.map (positiveBoxOrigin φ d i + ·)) + (map_getD_zipIdx (profileFormula (clauseProfile c)) + (clauseGeometry φ d i c).clausePositions (0, 0) (clauseGeometry_clause_length φ d i c)) + simpa only [List.map_map, Function.comp_def, LocalGeometry.clausePosition] using he + +/-- Every actual local clause point lies in the common closed collar square. -/ +theorem clauseGeometry_clause_bounds (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) {p : Point} (hp : p ∈ (clauseGeometry φ d i c).clausePositions) : + InPositiveLocalBox p := by + obtain ⟨q, hq, rfl⟩ := List.mem_map.mp hp + apply collarPoint_in_positiveBox + have hb := profile_prefix_clause_interior (clauseProfile c) q hq + exact ⟨hb.1.le, hb.2.1.le, hb.2.2.1.le, hb.2.2.2.le⟩ + +/-- A single actual clause box has distinct clause points. -/ +theorem positiveClauseBlock_positions_nodup (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) : + ((positiveClauseBlock φ d i c).map fun pc => pc.2.1).Nodup := by + rw [positiveClauseBlock_positions] + apply List.Nodup.map + · exact fun _ _ => add_left_cancel + · change ((profilePrefixGeometry (clauseProfile c)).clausePositions.map + (collarPoint (collarReflected (clausePorts φ d i)))).Nodup + exact ((List.nodup_append.mp (profilePrefixGeometry_check (clauseProfile c)).1).2.1).map + (collarPoint_injective _) + +/-- A clause-box point identifies its original source-clause owner. -/ +theorem positiveClauseBlock_positions_disjoint {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + {i j : ℕ} (hi : i < φ.length) (hj : j < φ.length) (hij : i ≠ j) + (c c' : Clause ℕ) : + List.Disjoint ((positiveClauseBlock φ d i c).map fun pc => pc.2.1) + ((positiveClauseBlock φ d j c').map fun pc => pc.2.1) := by + rw [positiveClauseBlock_positions, positiveClauseBlock_positions, List.disjoint_left] + intro p hpi hpj + obtain ⟨q, hq, hqp⟩ := List.mem_map.mp hpi + obtain ⟨r, hr, hrp⟩ := List.mem_map.mp hpj + have hc := positiveBox_owner_injective (clauseGeometry_clause_bounds φ d i c hq) + (clauseGeometry_clause_bounds φ d j c' hr) (hqp.trans hrp.symm) + have hl := drawingPosition_injOn hd (sourceClause_declared φ hi) (sourceClause_declared φ hj) hc + exact hij (Nat.add_left_cancel hl) + +/-- Every actual output clause has its own distinct displayed point. -/ +theorem positiveClausePositions_nodup {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) : + ((positivePlacedClauses φ d).map fun pc => pc.2.1).Nodup := by + rw [positivePlacedClauses, List.map_flatMap] + apply List.nodup_flatMap.mpr + constructor + · intro c _ + exact positiveClauseBlock_positions_nodup _ _ _ _ + · apply List.pairwise_iff_getElem.mpr + intro i j hi hj hij + have hi' : i < φ.length := by simpa only [List.length_zipIdx] using hi + have hj' : j < φ.length := by simpa only [List.length_zipIdx] using hj + simpa only [Function.onFun, List.getElem_zipIdx, Nat.zero_add] using + positiveClauseBlock_positions_disjoint hd hi' hj' (Nat.ne_of_lt hij) φ[i] φ[j] + +/-- Private local auxiliary points and actual local clause points are different. -/ +theorem clauseGeometry_auxiliary_ne_clause (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) {v : CNF.PositiveExactOneCompile.Variable ℕ} + (hv : v ∈ profileVariables (clauseProfile c)) (ha : ∀ n, v ≠ Sum.inl (Sum.inl n)) + {p : Point} (hp : p ∈ (clauseGeometry φ d i c).clausePositions) : + (clauseGeometry φ d i c).variablePosition + ((profileVariables (clauseProfile c)).idxOf v) ≠ p := by + obtain ⟨q, hq, rfl⟩ := List.mem_map.mp hp + rw [clauseGeometry_auxiliary_point φ d i c hv ha] + intro he + have he' := collarPoint_injective _ he + have hidx := profileVariableIndex_lt (clauseProfile c) hv + have hmem : (profilePrefixGeometry (clauseProfile c)).variablePosition + ((profileVariables (clauseProfile c)).idxOf v) ∈ + (profilePrefixGeometry (clauseProfile c)).variablePositions := by + rw [LocalGeometry.variablePosition, List.getD_eq_getElem _ _ hidx] + exact List.getElem_mem hidx + exact (List.nodup_append.mp (profilePrefixGeometry_check (clauseProfile c)).1).2.2 _ hmem _ hq he' + +/-- No actual source or private variable point coincides with an actual clause point. -/ +theorem positiveVariablePoint_ne_clause {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + {v : CNF.PositiveExactOneCompile.Variable ℕ} + (hv : v ∈ (CNF.PositiveExactOneCompile.rawFormula φ).vars) {p : Point} + (hp : p ∈ (positivePlacedClauses φ d).map fun pc => pc.2.1) : + positiveVariablePoint φ d v ≠ p := by + obtain ⟨c', hc', hp⟩ := List.mem_flatMap.mp (by + simpa only [positivePlacedClauses, List.map_flatMap] using hp) + rcases c' with ⟨c, i⟩ + have hp' := hp + rw [positiveClauseBlock_positions] at hp' + obtain ⟨q, hq, hqp⟩ := List.mem_map.mp hp' + obtain ⟨hi, hci⟩ := List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hc') + obtain ⟨vp, hvp⟩ := positivePositionTable_covers φ d hv + rw [positiveVariablePoint_of_mem φ d (positivePositionTable_keys_nodup φ d) hvp] + intro he + rcases List.mem_append.mp hvp with hsource | haux + · obtain ⟨n, hn, hne⟩ := List.mem_map.mp hsource + have hnp := congrArg Prod.snd hne + have hbox : InPositiveLocalBox positiveTranslation := by + norm_num [InPositiveLocalBox, positiveTranslation, positiveRadius] + have hcenters := positiveBox_owner_injective hbox (clauseGeometry_clause_bounds φ d i c hq) + (hnp.trans (he.trans hqp.symm)) + have hlabels := drawingPosition_injOn hd + (sourceVariable_declared φ (CNF.mem_distinctVars.mp hn)) (sourceClause_declared φ hi) hcenters + have hlt := List.idxOf_lt_length_of_mem hn + omega + · obtain ⟨c'', j, hcj, w, hw, hwa, _, hwp⟩ := positiveAuxiliaryPositions_origin φ d haux + obtain ⟨hj, hcj'⟩ := List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hcj) + have hplaced := hwp.symm.trans (he.trans hqp.symm) + have hcenters := positiveBox_owner_injective (clauseGeometry_auxiliary_bounds φ d j c'' hw hwa) + (clauseGeometry_clause_bounds φ d i c hq) hplaced + have hlabels := drawingPosition_injOn hd (sourceClause_declared φ hj) + (sourceClause_declared φ hi) hcenters + have hji : j = i := Nat.add_left_cancel hlabels + subst j + have hcc : c'' = c := hcj'.symm.trans hci + subst c'' + exact clauseGeometry_auxiliary_ne_clause φ d i c hw hwa hq (add_left_cancel hplaced) + +/-- Every actual output graph vertex has a distinct displayed point. -/ +theorem positiveDrawing_positions_nodup {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) : + (positiveDrawing φ d).1.Nodup := by + rw [positiveDrawing, List.nodup_append] + refine ⟨positiveVariablePositions_nodup hd, positiveClausePositions_nodup hd, ?_⟩ + intro p hp q hq he + obtain ⟨v, hv, rfl⟩ := List.mem_map.mp hp + exact positiveVariablePoint_ne_clause hd (CNF.mem_distinctVars.mp hv) hq he + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingCosts.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingCosts.lean new file mode 100644 index 000000000..9017ec7c4 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingCosts.lean @@ -0,0 +1,386 @@ +/- +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.ExactOne.DrawingFits +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingRoutes +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingCounts +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesPrefix +import Complexity.Logics.Propositional.CNF.Occurrence +import Mathlib.Data.List.GetD +import Mathlib.Algebra.BigOperators.Ring.List +import Mathlib.Algebra.Order.BigOperators.Group.List +import Mathlib.Algebra.Order.Ring.Nat +import Mathlib.Tactic.Linarith + +/-! +# Constant-factor costs of the actual positive drawing compiler + +Costs are counted on the actual emitted route lists. The source-slot sequence of every fixed +profile contains each source occurrence once. Consequently inherited exteriors are charged +once, while every original clause receives only a fixed local route, segment and wire budget. + +## Main results + +* `clauseGeometry_local_costs`: fixed segment and wire budgets for the actual local replacements. +* `positiveDrawing_costs`: constant-factor actual vertex, route, segment and wire bounds. + +## References + +The exact finite-list accounting and cost estimates are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +private def sourceSlot (p : ClauseProfile) (e : LocalIncidence) : Option ℕ := + match (profileVariables p).getD e.2.1 (.inl (.inl 0)) with + | .inl (.inl j) => some j + | _ => none + +private theorem prefix_sourceSlots (p : ClauseProfile) : + ((profilePrefixGeometry p).incidenceRoutes.filterMap (sourceSlot p)) = + (profileSource p).zipIdx.map Prod.snd := by + fin_cases p <;> decide +kernel + +private theorem prefix_routes_grouped (p : ClauseProfile) : + (profileFormula p).zipIdx.flatMap (fun a => + (profilePrefixGeometry p).incidenceRoutes.filter fun e => e.1 == a.2) = + (profilePrefixGeometry p).incidenceRoutes := by + fin_cases p <;> decide +kernel + +private theorem clause_sourceSlots (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) : + ((clauseGeometry φ d i c).incidenceRoutes.filterMap (sourceSlot (clauseProfile c))) = + (profileSource (clauseProfile c)).zipIdx.map Prod.snd := by + simp only [clauseGeometry, collaredPrefixGeometry, List.filterMap_map] + change ((profilePrefixGeometry (clauseProfile c)).incidenceRoutes.filterMap + (sourceSlot (clauseProfile c))) = _ + exact prefix_sourceSlots (clauseProfile c) + +private theorem clause_routes_grouped (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) : + (profileFormula (clauseProfile c)).zipIdx.flatMap (fun a => + (clauseGeometry φ d i c).incidenceRoutes.filter fun e => e.1 == a.2) = + (clauseGeometry φ d i c).incidenceRoutes := by + let g := profilePrefixGeometry (clauseProfile c) + let s := clausePorts φ d i + let f (e : LocalIncidence) : LocalIncidence := + (e.1, e.2.1, if e.2.1 ∈ g.terminalVariables then + collarPrefixRoute s (terminalIndex g e.2.1) e.2.2 + else collarRoute (collarReflected s) e.2.2) + simpa only [clauseGeometry, collaredPrefixGeometry, List.map_flatMap, List.filter_map, + Function.comp_def] using congrArg (List.map f) (prefix_routes_grouped (clauseProfile c)) + +private theorem sum_option_weights {A : Type*} (xs : List A) (slot : A → Option ℕ) + (f : ℕ → ℕ) : + (xs.map fun a => (slot a).elim 0 f).sum = ((xs.filterMap slot).map f).sum := by + induction xs with + | nil => rfl + | cons a xs ih => cases h : slot a <;> simp [h, ih] + +private theorem clause_source_weight (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) (hw : c.length ≤ 3) (f : ℕ → ℕ) : + ((clauseGeometry φ d i c).incidenceRoutes.map fun e => + (sourceSlot (clauseProfile c) e).elim 0 f).sum = + (c.zipIdx.map fun a => f a.2).sum := by + rw [sum_option_weights, clause_sourceSlots] + have he : (profileSource (clauseProfile c)).zipIdx.map Prod.snd = c.zipIdx.map Prod.snd := by + simp only [List.zipIdx_map_snd, profileSource_length c hw] + rw [he, List.map_map] + rfl + +private theorem positiveDrawing_weight (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (weight : Route → ℕ) : + (((positiveDrawing φ d).2).map weight).sum = + (φ.zipIdx.map fun a => ((clauseGeometry φ d a.2 a.1).incidenceRoutes.map fun e => + weight (positiveIncidenceRoute φ d a.2 a.1 e)).sum).sum := by + have hlocal (c : Clause ℕ) (i : ℕ) : + (((positiveClauseBlock φ d i c).flatMap fun a => a.2.2).map weight).sum = + ((clauseGeometry φ d i c).incidenceRoutes.map fun e => + weight (positiveIncidenceRoute φ d i c e)).sum := by + have he := congrArg (fun es => (es.map fun e => + weight (positiveIncidenceRoute φ d i c e)).sum) (clause_routes_grouped φ d i c) + simpa only [positiveClauseBlock, List.flatMap_map, List.map_flatMap, List.map_map, + Function.comp_def] using he + change (((positivePlacedClauses φ d).flatMap fun c => c.2.2).map weight).sum = _ + rw [positivePlacedClauses, List.flatMap_assoc, List.map_flatMap] + rw [List.flatMap_def, List.sum_flatten, List.map_map] + apply congrArg List.sum + apply List.map_congr_left + intro a _ + exact hlocal a.1 a.2 + +private theorem segmentCount_eq (r : Route) : r.consecutivePairs.length = r.length - 1 := by + simp only [List.consecutivePairs, List.length_zip, List.length_tail] + omega + +/-- The actual collared local template has bounded total segment and Manhattan-wire costs. +The conservative collar allowance is charged once per local incidence. -/ +theorem clauseGeometry_local_costs {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) (c : Clause ℕ) : + (((clauseGeometry φ d i c).incidenceRoutes.map fun e => e.2.2.consecutivePairs.length).sum ≤ + 575) ∧ + (((clauseGeometry φ d i c).incidenceRoutes.map fun e => wireLength e.2.2).sum ≤ + 40252600) := by + let p := clauseProfile c + let g := profilePrefixGeometry p + let s := clausePorts φ d i + have hs := (clausePorts_spec hd hi).1 + have hg := profilePrefixGeometry_check p + have hmeta := profilePrefixGeometry_costs p + have hseg (e : LocalIncidence) (he : e ∈ g.incidenceRoutes) : + (profileCollarRoute p s e).consecutivePairs.length ≤ e.2.2.consecutivePairs.length + 7 := by + unfold profileCollarRoute + split_ifs with ht + · have hend := (LocalGeometry.prefix_incidence_endpoints hg he).2.2.2 + rw [profile_prefix_terminal_endpoint p e.2.1 ht] at hend + rw [consecutivePairs_length_collarPrefixRoute hs hend] + exact Nat.add_le_add_left ((collar_route_costs s hs _).1) _ + · rw [consecutivePairs_length_collarRoute] + omega + have hwire (e : LocalIncidence) (he : e ∈ g.incidenceRoutes) : + wireLength (profileCollarRoute p s e) ≤ wireLength e.2.2 + 600000 := by + unfold profileCollarRoute + split_ifs with ht + · have hend := (LocalGeometry.prefix_incidence_endpoints hg he).2.2.2 + rw [profile_prefix_terminal_endpoint p e.2.1 ht] at hend + rw [wireLength_collarPrefixRoute hs hend] + exact Nat.add_le_add_left ((collar_route_costs s hs _).2) _ + · rw [wireLength_collarRoute] + omega + have hsc : ((g.incidenceRoutes.map fun e => e.2.2.consecutivePairs.length).sum) ≤ 113 := by + simpa only [segmentCount_eq] using hmeta.2.2.2.1 + have hlen : g.incidenceRoutes.length ≤ 66 := hmeta.2.2.1 + have hwc : (g.incidenceRoutes.map fun e => wireLength e.2.2).sum ≤ 652600 := hmeta.2.2.2.2.1 + have hs' := List.sum_le_sum hseg + have hw' := List.sum_le_sum hwire + have hconst (n : ℕ) : (g.incidenceRoutes.map fun _ => n).sum = + g.incidenceRoutes.length * n := by + rw [show g.incidenceRoutes.map (fun _ => n) = + List.replicate g.incidenceRoutes.length n from List.map_const, List.sum_replicate] + rfl + rw [List.sum_map_add, hconst] at hs' hw' + constructor + · simp only [clauseGeometry, collaredPrefixGeometry, List.map_map, Function.comp_def] + change (g.incidenceRoutes.map fun e => (profileCollarRoute p s e).consecutivePairs.length).sum + ≤ _ + nlinarith + · simp only [clauseGeometry, collaredPrefixGeometry, List.map_map, Function.comp_def] + change (g.incidenceRoutes.map fun e => wireLength (profileCollarRoute p s e)).sum ≤ _ + nlinarith + +private theorem positiveIncidenceRoute_wireLength {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) {e : LocalIncidence} + (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) : + wireLength (positiveIncidenceRoute φ d i φ[i] e) = wireLength e.2.2 + + (sourceSlot (clauseProfile φ[i]) e).elim 0 + (fun j => wireLength (positiveExterior φ d i j)) := by + unfold positiveIncidenceRoute sourceSlot + generalize hget : (profileVariables (clauseProfile φ[i])).getD e.2.1 (.inl (.inl 0)) = v + rcases v with (j | _) | _ + · obtain ⟨q, hlast, hhead⟩ := positiveIncidenceRoute_join_endpoints hd hi + (profilePrefixGeometry_check _) he hget + change wireLength (positiveStem φ d i e ++ (positiveExterior φ d i j).tail) = + wireLength e.2.2 + wireLength (positiveExterior φ d i j) + rw [wireLength_join hlast hhead] + simp only [positiveStem, wireLength_translate] + all_goals simp [wireLength_translate] + +private theorem positiveIncidenceRoute_segmentCount {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) {e : LocalIncidence} + (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) : + (positiveIncidenceRoute φ d i φ[i] e).consecutivePairs.length = + e.2.2.consecutivePairs.length + (sourceSlot (clauseProfile φ[i]) e).elim 0 + (fun j => (positiveExterior φ d i j).consecutivePairs.length) := by + unfold positiveIncidenceRoute sourceSlot + generalize hget : (profileVariables (clauseProfile φ[i])).getD e.2.1 (.inl (.inl 0)) = v + rcases v with (j | _) | _ + · obtain ⟨q, hlast, hhead⟩ := positiveIncidenceRoute_join_endpoints hd hi + (profilePrefixGeometry_check _) he hget + change (positiveStem φ d i e ++ (positiveExterior φ d i j).tail).consecutivePairs.length = + e.2.2.consecutivePairs.length + (positiveExterior φ d i j).consecutivePairs.length + rw [consecutivePairs_join hlast hhead, List.length_append] + simp only [positiveStem, segmentCount_eq, translate, List.length_map] + all_goals simp [translate] + +private theorem sourceEdgeIndex_occurrence (φ : CNF ℕ) {o : Occurrence ℕ} + (ho : o ∈ Cslib.Logic.PL.occurrences φ) : + sourceEdgeIndex φ o.1 o.2.1 = (Cslib.Logic.PL.occurrences φ).idxOf o := by + obtain ⟨hi, hj, he⟩ := Cslib.Logic.PL.mem_occurrences.mp ho + unfold sourceEdgeIndex + congr 1 + unfold sourceOccurrence + rw [List.getD_eq_getElem _ _ hi, List.getD_eq_getElem _ _ hj, ← he] + +private theorem source_edge_weight_sum {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) (weight : Route → ℕ) : + (φ.zipIdx.map fun a => (a.1.zipIdx.map fun l => + weight (d.2.getD (sourceEdgeIndex φ a.2 l.2) [])).sum).sum = (d.2.map weight).sum := by + have hlen : (Cslib.Logic.PL.occurrences φ).length = d.2.length := by + simpa only [SAT.incidenceGraph, List.length_map] using hd.edgeCount.symm + have hlist : ((Cslib.Logic.PL.occurrences φ).map fun o => + weight (d.2.getD ((Cslib.Logic.PL.occurrences φ).idxOf o) [])) = d.2.map weight := by + apply List.ext_getElem (by simp only [List.length_map, hlen]) + intro i hi hj + have hi' : i < (Cslib.Logic.PL.occurrences φ).length := by simpa only [List.length_map] using hi + have hj' : i < d.2.length := by simpa only [List.length_map] using hj + rw [List.getElem_map, (Cslib.Logic.PL.occurrences_nodup φ).idxOf_getElem i hi', + List.getD_eq_getElem _ _ hj', List.getElem_map] + calc + _ = ((Cslib.Logic.PL.occurrences φ).map fun o => + weight (d.2.getD (sourceEdgeIndex φ o.1 o.2.1) [])).sum := by + rw [Cslib.Logic.PL.occurrences, List.map_flatMap, List.flatMap_def, + List.sum_flatten, List.map_map] + simp only [List.map_map, Function.comp_def] + _ = ((Cslib.Logic.PL.occurrences φ).map fun o => + weight (d.2.getD ((Cslib.Logic.PL.occurrences φ).idxOf o) [])).sum := by + apply congrArg List.sum + exact List.map_congr_left fun o ho => by rw [sourceEdgeIndex_occurrence φ ho] + _ = _ := congrArg List.sum hlist + +private theorem segmentCount_translate (v : Point) (r : Route) : + (translate v r).consecutivePairs.length = r.consecutivePairs.length := by + simp only [segmentCount_eq, translate, List.length_map] + +private theorem exterior_segment_sum {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) : + (φ.zipIdx.map fun a => (a.1.zipIdx.map fun l => + (positiveExterior φ d a.2 l.2).consecutivePairs.length).sum).sum = + Graph.drawingSegmentCount d := by + simp only [positiveExterior, segmentCount_translate, exteriorRoute_segmentCount] + exact source_edge_weight_sum hd (fun r : Route => r.consecutivePairs.length) + +private theorem exterior_wire_sum_le {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) : + (φ.zipIdx.map fun a => (a.1.zipIdx.map fun l => + wireLength (positiveExterior φ d a.2 l.2)).sum).sum ≤ + 1080000 * Graph.drawingWireLength d := by + calc + _ ≤ (φ.zipIdx.map fun a => (a.1.zipIdx.map fun l => + 1080000 * wireLength (d.2.getD (sourceEdgeIndex φ a.2 l.2) [])).sum).sum := by + apply List.sum_le_sum + intro ⟨c, i⟩ hci + obtain ⟨hi, hget⟩ := List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hci) + change φ[i] = c at hget + rw [← hget] + apply List.sum_le_sum + intro ⟨l, j⟩ hlj + have hj : j < φ[i].length := + (List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hlj)).choose + have he := exteriorRoute_wireLength hd (sourceEdgeIndex_lt φ hi hj) + (by decide : positiveRadius < positiveScale) + have hw : wireLength (positiveExterior φ d i j) + positiveRadius = + 1080000 * wireLength (d.2.getD (sourceEdgeIndex φ i j) []) := by + simpa only [positiveExterior, wireLength_translate, positiveScale] using he + change wireLength (positiveExterior φ d i j) ≤ + 1080000 * wireLength (d.2.getD (sourceEdgeIndex φ i j) []) + omega + _ = 1080000 * (φ.zipIdx.map fun a => (a.1.zipIdx.map fun l => + wireLength (d.2.getD (sourceEdgeIndex φ a.2 l.2) [])).sum).sum := by + simp only [List.sum_map_mul_left] + _ = _ := by rw [source_edge_weight_sum hd]; rfl + +private theorem sum_map_constant {A : Type*} (xs : List A) (n : ℕ) : + (xs.map fun _ => n).sum = xs.length * n := by + rw [show xs.map (fun _ => n) = List.replicate xs.length n from List.map_const, + List.sum_replicate] + rfl + +/-- The actual route list has at most 507 incidences per original clause. -/ +theorem positiveDrawing_routeCount (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + (positiveDrawing φ d).2.length ≤ 507 * φ.length := by + rw [positiveDrawing_edgeCount, SAT.length_edges_incidenceGraph] + exact CNF.PositiveExactOneCompile.length_vars_formula_le φ + +/-- The actual vertex list has at most 676 vertices per original clause. -/ +theorem positiveDrawing_vertexCount_le (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + (positiveDrawing φ d).1.length ≤ 676 * φ.length := by + rw [positiveDrawing_vertexCount, SAT.length_vertices_incidenceGraph] + have hv := (CNF.PositiveExactOneCompile.formula φ).numVars_le_length_vars + have hl := CNF.PositiveExactOneCompile.length_vars_formula_le φ + have hc := CNF.PositiveExactOneCompile.length_formula_le φ + omega + +/-- Each original source segment is retained once; every original clause contributes only +a fixed local segment allowance. -/ +theorem positiveDrawing_segmentCount {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + (hw : ∀ c ∈ φ, c.length ≤ 3) : + Graph.drawingSegmentCount (positiveDrawing φ d) ≤ + 575 * φ.length + Graph.drawingSegmentCount d := by + rw [Graph.drawingSegmentCount, positiveDrawing_weight] + calc + _ ≤ (φ.zipIdx.map fun a => 575 + (a.1.zipIdx.map fun l => + (positiveExterior φ d a.2 l.2).consecutivePairs.length).sum).sum := by + apply List.sum_le_sum + intro ⟨c, i⟩ hci + obtain ⟨hi, hget⟩ := List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hci) + change φ[i] = c at hget + rw [← hget] + have heq := List.map_congr_left fun e + (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) => + positiveIncidenceRoute_segmentCount hd hi he + rw [heq, List.sum_map_add, + clause_source_weight φ d i φ[i] (hw _ (List.getElem_mem hi))] + exact Nat.add_le_add_right (clauseGeometry_local_costs hd hi φ[i]).1 _ + _ = 575 * φ.length + (φ.zipIdx.map fun a => (a.1.zipIdx.map fun l => + (positiveExterior φ d a.2 l.2).consecutivePairs.length).sum).sum := by + rw [List.sum_map_add, sum_map_constant] + simp only [List.length_zipIdx, Nat.mul_comm] + _ = _ := by rw [exterior_segment_sum hd] + +/-- Each inherited source wire is charged once after the fixed dilation. The rest is a +constant local wire allowance per original clause. -/ +theorem positiveDrawing_wireLength {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + (hw : ∀ c ∈ φ, c.length ≤ 3) : + Graph.drawingWireLength (positiveDrawing φ d) ≤ + 40252600 * φ.length + 1080000 * Graph.drawingWireLength d := by + rw [Graph.drawingWireLength, positiveDrawing_weight] + calc + _ ≤ (φ.zipIdx.map fun a => 40252600 + (a.1.zipIdx.map fun l => + wireLength (positiveExterior φ d a.2 l.2)).sum).sum := by + apply List.sum_le_sum + intro ⟨c, i⟩ hci + obtain ⟨hi, hget⟩ := List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hci) + change φ[i] = c at hget + rw [← hget] + have heq := List.map_congr_left fun e + (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) => + positiveIncidenceRoute_wireLength hd hi he + rw [heq, List.sum_map_add, + clause_source_weight φ d i φ[i] (hw _ (List.getElem_mem hi))] + exact Nat.add_le_add_right (clauseGeometry_local_costs hd hi φ[i]).2 _ + _ = 40252600 * φ.length + (φ.zipIdx.map fun a => (a.1.zipIdx.map fun l => + wireLength (positiveExterior φ d a.2 l.2)).sum).sum := by + rw [List.sum_map_add, sum_map_constant] + simp only [List.length_zipIdx, Nat.mul_comm] + _ ≤ _ := Nat.add_le_add_left (exterior_wire_sum_le hd) _ + +/-- All actual emitted drawing costs grow by only fixed factors. Inherited exterior incidences +are counted exactly once, independently of source variable repetitions. -/ +theorem positiveDrawing_costs {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + (hw : ∀ c ∈ φ, c.length ≤ 3) : + (positiveDrawing φ d).1.length ≤ 676 * φ.length ∧ + (positiveDrawing φ d).2.length ≤ 507 * φ.length ∧ + Graph.drawingSegmentCount (positiveDrawing φ d) ≤ + 575 * φ.length + Graph.drawingSegmentCount d ∧ + Graph.drawingWireLength (positiveDrawing φ d) ≤ + 40252600 * φ.length + 1080000 * Graph.drawingWireLength d := + ⟨positiveDrawing_vertexCount_le φ d, positiveDrawing_routeCount φ d, + positiveDrawing_segmentCount hd hw, positiveDrawing_wireLength hd hw⟩ + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingCounts.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingCounts.lean new file mode 100644 index 000000000..2a5e346df --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingCounts.lean @@ -0,0 +1,90 @@ +/- +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.ExactOne.Drawing +import Mathlib.Data.List.GetD + +/-! +# Exact incidence counts of the positive drawing compiler + +Positions and routes are counted on the emitted lists. In particular, source placeholders are +absent from the actual vertex list, and splicing never introduces an additional graph edge. + +## Main results + +* `positiveDrawing_vertexCount`: the exact actual incidence-graph vertex count. +* `positiveDrawing_edgeCount`: one actual route for each actual output literal occurrence. + +## References + +The finite-profile counting and incidence-indexing proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- Collars and source-boundary extensions preserve every local incidence's clause index. -/ +theorem clauseGeometry_incidence_count (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) (j : Fin 22) : + ((clauseGeometry φ d i c).incidenceRoutes.filter fun e => e.1 == j.val).length = + ((profileFormula (clauseProfile c)).getD j.val []).length := by + simpa only [clauseGeometry, collaredPrefixGeometry, profilePrefixGeometry, + LocalGeometry.toPrefix, List.filter_map, List.length_map, Function.comp_def] using + profileGeometry_incidence_count (clauseProfile c) j + +/-- Every placed actual clause has exactly one route per literal position. -/ +theorem positiveClauseBlock_route_count (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) {pc : PositivePlacedClause} (hpc : pc ∈ positiveClauseBlock φ d i c) : + pc.2.2.length = pc.1.length := by + obtain ⟨⟨cl, j⟩, hj, rfl⟩ := List.mem_map.mp hpc + have hj' := List.mem_zipIdx_iff_getElem?.mp hj + have hindex : j < (profileFormula (clauseProfile c)).length := + (List.getElem?_eq_some_iff.mp hj').choose + have hbound := profileFormula_length_le (clauseProfile c) + have he : (profileFormula (clauseProfile c)).getD j [] = cl := by + rw [List.getD_eq_getElem?_getD, hj'] + rfl + simpa only [List.length_map, Clause.length_relabel, he] using + clauseGeometry_incidence_count φ d i c ⟨j, by omega⟩ + +/-- Every actual emitted clause has exactly one actual incidence route per literal. -/ +theorem positivePlacedClauses_route_count (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + {pc : PositivePlacedClause} (hpc : pc ∈ positivePlacedClauses φ d) : + pc.2.2.length = pc.1.length := by + obtain ⟨c, _, hc⟩ := List.mem_flatMap.mp hpc + exact positiveClauseBlock_route_count φ d c.2 c.1 hc + +/-- The actual output drawing has exactly one position per actual output graph vertex. -/ +theorem positiveDrawing_vertexCount (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + (positiveDrawing φ d).1.length = + (SAT.incidenceGraph (CNF.PositiveExactOneCompile.formula φ)).1.length := by + have hlen := congrArg List.length (positivePlacedClauses_formula φ d) + rw [CNF.PositiveExactOneCompile.formula, SAT.incidenceGraph_compact] + simp only [positiveDrawing, List.length_append, List.length_map, + SAT.incidenceGraph, List.length_range] + simpa only [List.length_map] using + congrArg ((CNF.PositiveExactOneCompile.rawFormula φ).distinctVars.length + ·) hlen + +/-- Splicing preserves one route per actual literal occurrence, in the emitted finite lists. -/ +theorem positiveDrawing_edgeCount (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + (positiveDrawing φ d).2.length = + (SAT.incidenceGraph (CNF.PositiveExactOneCompile.formula φ)).2.length := by + rw [CNF.PositiveExactOneCompile.formula, SAT.incidenceGraph_compact, + SAT.length_edges_incidenceGraph] + change ((positivePlacedClauses φ d).flatMap fun c => c.2.2).length = _ + rw [← positivePlacedClauses_formula φ d, CNF.length_vars, List.length_flatMap, List.map_map] + congr 1 + apply List.map_congr_left + intro pc hpc + exact positivePlacedClauses_route_count φ d hpc + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingData.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingData.lean new file mode 100644 index 000000000..3584d8085 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingData.lean @@ -0,0 +1,227 @@ +/- +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.Graph.OrthogonalDrawing +public import Complexity.Computability.Complexity.Problems.SAT.Incidence.Compact +public import Complexity.Logics.Propositional.CNF.Occurrence +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Nodup + +/-! +# Incidence-order assembly of clause-replacement drawings + +The drawing lists follow the actual formula: variables use first-occurrence order, clauses use +list order, and routes use literal-occurrence order. This interface bridges local replacement +geometry to `Graph.IsPlanarOrthogonalDrawing` without changing the output incidence graph. + +## Main definitions + +* `FormulaDrawing`: variable positions, clause positions, and occurrence-indexed routes. +* `FormulaDrawing.drawing`: the finite lists of the actual incidence drawing. +* `FormulaDrawing.Valid`: continuous geometric conditions on those data. + +## Main results + +* `FormulaDrawing.isPlanarOrthogonalDrawing`: the assembled drawing has the actual incidence graph. +* `FormulaDrawing.isPlanarOrthogonalDrawing_compact`: compact numbering preserves that graph. + +## References + +The finite assembly and its indexing proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +variable {V : Type} [DecidableEq V] + +/-- Geometric data indexed by the variables, clauses, and literal positions of a fixed formula. -/ +structure FormulaDrawing (φ : CNF V) where + /-- The displayed position of each occurring variable. Values outside the support are ignored. -/ + variablePoint : V → Point + /-- Clause positions, in formula-list order. -/ + clausePoints : List Point + /-- A clause-to-variable route for every actual literal occurrence. -/ + incidenceRoute : Occurrence V → Route + +namespace FormulaDrawing + +/-- Variable vertices precede clause vertices in the actual incidence graph. -/ +def positions {φ : CNF V} (a : FormulaDrawing φ) : List Point := + φ.distinctVars.map a.variablePoint ++ a.clausePoints + +/-- Routes retain the actual formula's literal-occurrence order. -/ +def routes {φ : CNF V} (a : FormulaDrawing φ) : List Route := + (Cslib.Logic.PL.occurrences φ).map a.incidenceRoute + +/-- Finite drawing data for the actual listed incidence graph. -/ +def drawing {φ : CNF V} (a : FormulaDrawing φ) : Graph.OrthogonalDrawing := + (a.positions, a.routes) + +/-- Geometric replacement certificates are stated on actual literal occurrences, including +repeated literals and repeated clause records. All intersections concern full real supports. -/ +structure Valid {φ : CNF V} (a : FormulaDrawing φ) : Prop where + /-- There is exactly one displayed clause vertex per clause position. -/ + clauseCount : a.clausePoints.length = φ.length + /-- No distinct formula vertices share their displayed positions. -/ + positionsNodup : a.positions.Nodup + /-- Every route has the actual clause and variable endpoints. -/ + endpoints : ∀ o ∈ Cslib.Logic.PL.occurrences φ, + (a.incidenceRoute o).head? = some (a.clausePoints.getD o.1 (0, 0)) ∧ + (a.incidenceRoute o).getLast? = some (a.variablePoint o.2.2.1) + /-- Routes are proper and orthogonal. -/ + orthogonal : ∀ o ∈ Cslib.Logic.PL.occurrences φ, IsOrthogonal (a.incidenceRoute o) + /-- Routes have no self-crossings or retracing. -/ + simple : ∀ o ∈ Cslib.Logic.PL.occurrences φ, IsSimple (a.incidenceRoute o) + /-- Only the incident variable vertex can lie on a route. -/ + avoidsVariables : ∀ o ∈ Cslib.Logic.PL.occurrences φ, ∀ v ∈ φ.vars, + realPoint (a.variablePoint v) ∈ support (a.incidenceRoute o) → v = o.2.2.1 + /-- Only the incident clause vertex can lie on a route. -/ + avoidsClauses : ∀ o ∈ Cslib.Logic.PL.occurrences φ, ∀ i < φ.length, + realPoint (a.clausePoints.getD i (0, 0)) ∈ support (a.incidenceRoute o) → i = o.1 + /-- Different occurrence routes meet only at their shared actual variable or clause vertex. -/ + contacts : ∀ o ∈ Cslib.Logic.PL.occurrences φ, + ∀ t ∈ Cslib.Logic.PL.occurrences φ, o ≠ t → + ∀ p ∈ support (a.incidenceRoute o), p ∈ support (a.incidenceRoute t) → + (o.2.2.1 = t.2.2.1 ∧ p = realPoint (a.variablePoint o.2.2.1)) ∨ + (o.1 = t.1 ∧ p = realPoint (a.clausePoints.getD o.1 (0, 0))) + +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 + +/-- An occurring variable resolves to its supplied position in the actual graph. -/ +theorem drawingPosition_variable {φ : CNF V} (a : FormulaDrawing φ) {v : V} + (hv : v ∈ φ.vars) : + Graph.drawingPosition (SAT.incidenceGraph φ) a.drawing (φ.distinctVars.idxOf v) = + a.variablePoint v := by + have hi := List.idxOf_lt_length_of_mem (CNF.mem_distinctVars.mpr hv) + change a.positions.getD ((List.range _).idxOf (φ.distinctVars.idxOf v)) (0, 0) = _ + rw [idxOf_range_eq (by omega), positions] + rw [List.getD_append _ _ _ _ (by simpa only [List.length_map] using hi)] + rw [List.getD_eq_getElem _ _ (by simpa only [List.length_map] using hi), List.getElem_map] + congr 1 + exact List.getElem_idxOf hi + +/-- A clause index resolves to its supplied position in the actual graph. -/ +theorem drawingPosition_clause {φ : CNF V} (a : FormulaDrawing φ) (_ha : a.Valid) + {i : ℕ} (hi : i < φ.length) : + Graph.drawingPosition (SAT.incidenceGraph φ) a.drawing (φ.distinctVars.length + i) = + a.clausePoints.getD i (0, 0) := by + change a.positions.getD ((List.range _).idxOf (φ.distinctVars.length + i)) (0, 0) = _ + rw [idxOf_range_eq (by omega), positions] + rw [List.getD_append_right _ _ _ _ (by simp only [List.length_map]; omega)] + simp only [List.length_map, Nat.add_sub_cancel_left] + +/-- The occurrence at an edge-list index is the same occurrence used to build its route. -/ +theorem indexed_route {φ : CNF V} (a : FormulaDrawing φ) {i : ℕ} + (hi : i < (SAT.incidenceGraph φ).2.length) : + a.routes.getD i [] = a.incidenceRoute + ((Cslib.Logic.PL.occurrences φ)[i]'(by simpa [SAT.incidenceGraph] using hi)) := by + have hi' : i < (Cslib.Logic.PL.occurrences φ).length := by + simpa [SAT.incidenceGraph] using hi + rw [routes, List.getD_eq_getElem _ _ (by simpa using hi'), List.getElem_map] + +/-- Local occurrence-indexed certificates give a drawing of the actual formula incidence graph. -/ +theorem isPlanarOrthogonalDrawing {φ : CNF V} (a : FormulaDrawing φ) (ha : a.Valid) : + Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) a.drawing := by + have hov (o : Occurrence V) (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 + have hei (i : ℕ) (hi : i < (SAT.incidenceGraph φ).2.length) : + (SAT.incidenceGraph φ).2[i] = + (φ.distinctVars.idxOf ((Cslib.Logic.PL.occurrences φ)[i]'(by + simpa [SAT.incidenceGraph] using hi)).2.2.1, + φ.distinctVars.length + ((Cslib.Logic.PL.occurrences φ)[i]'(by + simpa [SAT.incidenceGraph] using hi)).1) := by + simp only [SAT.incidenceGraph, List.getElem_map] + refine ⟨?_, ?_, List.nodup_range, ha.positionsNodup, + (fun _ => SAT.incidenceGraph_endpoints), (fun _ => SAT.incidenceGraph_loopless), + ?_, ?_, ?_, ?_, ?_⟩ + · simp [drawing, positions, SAT.incidenceGraph, ha.clauseCount] + · simp [drawing, routes, SAT.incidenceGraph] + · intro i hi + have hi' : i < (Cslib.Logic.PL.occurrences φ).length := by + simpa [SAT.incidenceGraph] using hi + let o := (Cslib.Logic.PL.occurrences φ)[i] + have ho : o ∈ Cslib.Logic.PL.occurrences φ := List.getElem_mem hi' + have hci : o.1 < φ.length := (Cslib.Logic.PL.mem_occurrences.mp ho).choose + rw [hei i hi] + change (_ = _ ∧ _ = _) ∨ (_ = _ ∧ _ = _) + rw [drawingPosition_variable a (hov o ho), drawingPosition_clause a ha hci] + exact Or.inr (by simpa only [drawing, indexed_route a hi] using ha.endpoints o ho) + · intro i hi + rw [show a.drawing.2 = a.routes from rfl, indexed_route a hi] + exact ha.orthogonal _ (List.getElem_mem _) + · intro i hi + rw [show a.drawing.2 = a.routes from rfl, indexed_route a hi] + exact ha.simple _ (List.getElem_mem _) + · intro i hi v hv hp + have hi' : i < (Cslib.Logic.PL.occurrences φ).length := by + simpa [SAT.incidenceGraph] using hi + let o := (Cslib.Logic.PL.occurrences φ)[i] + have ho : o ∈ Cslib.Logic.PL.occurrences φ := List.getElem_mem hi' + rw [show a.drawing.2 = a.routes from rfl, indexed_route a hi] at hp + rw [hei i hi] + have hv' : v < φ.distinctVars.length + φ.length := by simpa [SAT.incidenceGraph] using hv + by_cases hvar : v < φ.distinctVars.length + · let w := φ.distinctVars[v] + have hw : w ∈ φ.vars := CNF.mem_distinctVars.mp (List.getElem_mem hvar) + have hidx : φ.distinctVars.idxOf w = v := (CNF.nodup_distinctVars φ).idxOf_getElem v hvar + rw [← hidx, drawingPosition_variable a hw] at hp + have he := ha.avoidsVariables o ho w hw hp + exact Or.inl (hidx.symm.trans (congrArg φ.distinctVars.idxOf he)) + · have hci : v - φ.distinctVars.length < φ.length := by omega + have hvci : v = φ.distinctVars.length + (v - φ.distinctVars.length) := by omega + rw [hvci, drawingPosition_clause a ha hci] at hp + have he := ha.avoidsClauses o ho _ hci hp + exact Or.inr (by change v = φ.distinctVars.length + o.1; omega) + · intro i hi j hj hij p hp hq + have hi' : i < (Cslib.Logic.PL.occurrences φ).length := by + simpa [SAT.incidenceGraph] using hi + have hj' : j < (Cslib.Logic.PL.occurrences φ).length := by + simpa [SAT.incidenceGraph] using hj + let o := (Cslib.Logic.PL.occurrences φ)[i] + let t := (Cslib.Logic.PL.occurrences φ)[j] + have ho : o ∈ Cslib.Logic.PL.occurrences φ := List.getElem_mem hi' + have ht : t ∈ Cslib.Logic.PL.occurrences φ := List.getElem_mem hj' + have hot : o ≠ t := fun h => hij ((Cslib.Logic.PL.occurrences_nodup φ).getElem_inj_iff.mp h) + rw [show a.drawing.2 = a.routes from rfl, indexed_route a hi] at hp + rw [show a.drawing.2 = a.routes from rfl, indexed_route a hj] at hq + rw [hei i hi, hei j hj] + rcases ha.contacts o ho t ht hot p hp hq with ⟨he, hp⟩ | ⟨he, hp⟩ + · refine ⟨φ.distinctVars.idxOf o.2.2.1, ?_, Or.inl rfl, Or.inl ?_, ?_⟩ + · have hidx := List.idxOf_lt_length_of_mem (CNF.mem_distinctVars.mpr (hov o ho)) + simp only [SAT.incidenceGraph, List.mem_range] + omega + · exact congrArg φ.distinctVars.idxOf he + · rw [drawingPosition_variable a (hov o ho)] + exact hp + · refine ⟨φ.distinctVars.length + o.1, ?_, Or.inr rfl, Or.inr ?_, ?_⟩ + · have hci := (Cslib.Logic.PL.mem_occurrences.mp ho).choose + simp only [SAT.incidenceGraph, List.mem_range] + omega + · exact congrArg (φ.distinctVars.length + ·) he + · rw [drawingPosition_clause a ha (Cslib.Logic.PL.mem_occurrences.mp ho).choose] + exact hp + +/-- The same actual drawing certifies compact output labels, with no change in any graph record. -/ +theorem isPlanarOrthogonalDrawing_compact {φ : CNF V} (a : FormulaDrawing φ) (ha : a.Valid) : + Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ.compact) a.drawing := by + rw [SAT.incidenceGraph_compact] + exact a.isPlanarOrthogonalDrawing ha + +end FormulaDrawing + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingEndpoints.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingEndpoints.lean new file mode 100644 index 000000000..250155fdb --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingEndpoints.lean @@ -0,0 +1,132 @@ +/- +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.ExactOne.Drawing +import Mathlib.Data.List.GetD +import Mathlib.Tactic.Abel + +/-! +# Matching actual local and exterior clause-box endpoints + +The selected local source slot ends at precisely the same integer point as the normalized, +scaled, trimmed original incidence. The far endpoint is the common actual source-variable +vertex, including when different source literal positions use the same variable. + +## Main results + +* `clauseGeometry_source_point`: actual local source-placeholder position. +* `positiveExterior_endpoints`: exact collar and shared-variable endpoints. + +## References + +These finite-coordinate matching and indexing proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- A valid local variable index resolves to its moved source port or its affine core point. -/ +theorem collared_variablePoint (g : LocalGeometry) (s : PortAssignment) + {v : ℕ} (hv : v < g.variablePositions.length) : + (collaredPrefixGeometry g s).variablePosition v = + if v ∈ g.terminalVariables then collarOuterPort (s.side (terminalIndex g v)) + else collarPoint (collarReflected s) (g.variablePosition v) := by + unfold LocalGeometry.variablePosition collaredPrefixGeometry + rw [List.getD_eq_getElem _ _ (by simpa using hv), List.getElem_map, List.getElem_zipIdx] + simp only [Nat.zero_add] + rw [List.getD_eq_getElem _ _ hv] + +/-- An exposed local variable resolves to its prescribed outer collar midpoint. -/ +theorem collared_variablePoint_terminal (g : LocalGeometry) (s : PortAssignment) + {v : ℕ} (hv : v < g.variablePositions.length) (ht : v ∈ g.terminalVariables) : + (collaredPrefixGeometry g s).variablePosition v = + collarOuterPort (s.side (terminalIndex g v)) := by + unfold LocalGeometry.variablePosition collaredPrefixGeometry + rw [List.getD_eq_getElem _ _ (by simpa using hv), List.getElem_map, List.getElem_zipIdx] + simp only [Nat.zero_add, ht, ↓reduceIte] + +/-- Every actual local clause position receives the common affine core placement. -/ +theorem collared_clausePoint (g : LocalGeometry) (s : PortAssignment) + {i : ℕ} (hi : i < g.clausePositions.length) : + (collaredPrefixGeometry g s).clausePosition i = + collarPoint (collarReflected s) (g.clausePosition i) := by + unfold LocalGeometry.clausePosition collaredPrefixGeometry + rw [List.getD_eq_getElem _ _ (by simpa using hi), List.getElem_map, + List.getD_eq_getElem _ _ hi] + +/-- The local source-slot position is the selected outer collar midpoint. -/ +theorem clauseGeometry_source_point (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) (j : Fin 3) (hj : j.val < (profileSource (clauseProfile c)).length) : + (clauseGeometry φ d i c).variablePosition + ((profileVariables (clauseProfile c)).idxOf (Sum.inl (Sum.inl j.val))) = + collarOuterPort ((clausePorts φ d i).side j) := by + have hv := profileSource_terminal_index (clauseProfile c) j hj + dsimp only at hv + have hlen : (profilePrefixGeometry (clauseProfile c)).variablePositions.length = + (profileGeometry (clauseProfile c)).variablePositions.length := by + simp only [profilePrefixGeometry, LocalGeometry.toPrefix, List.length_map, List.length_zipIdx] + have ht : (profileVariables (clauseProfile c)).idxOf (Sum.inl (Sum.inl j.val)) ∈ + (profilePrefixGeometry (clauseProfile c)).terminalVariables := hv.2.1 + rw [clauseGeometry, collared_variablePoint_terminal _ _ (by omega) ht] + congr 2 + apply Fin.ext + change ((profileGeometry (clauseProfile c)).terminalVariables.idxOf + ((profileVariables (clauseProfile c)).idxOf (Sum.inl (Sum.inl j.val)))) % 3 = j.val + rw [hv.2.2, Nat.mod_eq_of_lt j.isLt] + +/-- The exterior source incidence begins at the actual selected collar midpoint. -/ +theorem positiveExterior_head {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) (j : Fin 3) (hj : j.val < φ[i].length) : + (positiveExterior φ d i j).head? = + some (positiveBoxOrigin φ d i + collarOuterPort ((clausePorts φ d i).side j)) := by + have he := (exteriorRoute_endpoints hd (sourceEdgeIndex_lt φ hi hj) + positiveScale positiveRadius).1 + unfold positiveExterior translate + rw [List.head?_map, he, Option.map_some] + congr 1 + have hs : sourceStart (SAT.incidenceGraph φ) d (sourceEdgeIndex φ i j) = + Graph.drawingPosition (SAT.incidenceGraph φ) d (φ.distinctVars.length + i) := by + rw [sourceStart, sourceEdgeIndex_edge φ hi hj] + rw [collarOuterPort_eq_center_add, clausePorts_side hd hi j hj] + unfold literalSide + rw [sideDirection_directionSide (sourceDirection_unit _ _ _)] + rw [exteriorPort, hs] + unfold positiveBoxOrigin positiveTranslation positiveRadius + abel + +/-- The exterior route ends at the actual shared source-variable vertex. -/ +theorem positiveExterior_last {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i j : ℕ} + (hi : i < φ.length) (hj : j < φ[i].length) : + (positiveExterior φ d i j).getLast? = + some (positiveVariablePoint φ d (Sum.inl (Sum.inl φ[i][j].1))) := by + have he := (exteriorRoute_endpoints hd (sourceEdgeIndex_lt φ hi hj) + positiveScale positiveRadius).2 + have hv : φ[i][j].1 ∈ φ.vars := CNF.mem_vars.mpr + ⟨φ[i], List.getElem_mem hi, φ[i][j].2, List.getElem_mem hj⟩ + unfold positiveExterior translate + rw [List.getLast?_map, he, Option.map_some, positiveVariablePoint_source φ d hv] + congr 1 + rw [sourceFinish, sourceEdgeIndex_edge φ hi hj] + +/-- Actual inherited exterior routes have exactly the local port and common variable endpoints. -/ +theorem positiveExterior_endpoints {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) (j : Fin 3) (hj : j.val < φ[i].length) : + (positiveExterior φ d i j).head? = + some (positiveBoxOrigin φ d i + collarOuterPort ((clausePorts φ d i).side j)) ∧ + (positiveExterior φ d i j).getLast? = + some (positiveVariablePoint φ d (Sum.inl (Sum.inl φ[i][j].1))) := + ⟨positiveExterior_head hd hi j hj, positiveExterior_last hd hi hj⟩ + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingFinitePolyTime.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingFinitePolyTime.lean new file mode 100644 index 000000000..40c3d9cf6 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingFinitePolyTime.lean @@ -0,0 +1,276 @@ +/- +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.ExactOne.Drawing +public import Complexity.Computability.Complexity.Problems.SAT +public import Complexity.Computability.Complexity.Problems.Graph.OrthogonalDrawing.Encoding +public import Complexity.Logics.Propositional.CNF.ExactOne.PolyTime +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Finite +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Finite +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain +import Mathlib.Data.List.GetD +import Mathlib.Data.Fintype.Pi + +/-! +# Polynomial-time finite dispatch in the positive exact-one drawing compiler + +The local profiles and ordered port assignments range over fixed finite types. Native finite +case analysis certifies their variable, clause, and route tables under binary coordinates. +Dynamic clause/literal indices remain unary counters. Source-role classification is a lookup +in a fixed finite option table, avoiding recursion through the structured raw variable type. + +## Main definitions + +* `encLocalIncidence`: unary local clause/variable indices and binary-coordinate routes. +* `profileRole`, `profileRoleTable`: finite source-slot or internal-variable classification. + +## Main results + +* `computableInPolyTime_clauseProfile`, `computableInPolyTime_directionSide`. +* `computableInPolyTime_variablePositions`, `computableInPolyTime_incidenceRoutes`. +* `computableInPolyTime_profileRole`: polynomial lookup with an unbounded unary index. +* `computableInPolyTime_clausePorts_of_literalSide`: compose dynamic source exit computation + with the fixed finite collar completion. + +## References + +These are original computational certificates for the fixed drawing tables. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne.DrawingFinitePolyTime + +open Cslib.Logic.PL Orthogonal Turing.MultiTapeTM + +attribute [local fun_prop] ComputableInPolyTime.tidy + +/-- The existing literal encoding, with binary natural variable labels. -/ +abbrev encClause : Clause ℕ ↪ List Bool := encList (encPair encNat encBool) + +/-- Native encoding of an input formula and its supplied binary-coordinate drawing. -/ +abbrev encInput : CNF ℕ × Graph.OrthogonalDrawing ↪ List Bool := + encPair encCNF Graph.encOrthogonalDrawing + +/-- Unary local incidence indices and a binary-coordinate route. -/ +abbrev encLocalIncidence : LocalIncidence ↪ List Bool := + encPair encUnary (encPair encUnary encRoute) + +/-- Encodings of the two fixed finite dispatch tags. -/ +noncomputable abbrev encProfilePorts : ClauseProfile × PortAssignment ↪ List Bool := + encPair (encFinite ClauseProfile) (encFinite PortAssignment) + +/-- Structured logical variables retain the source's binary natural encoding. -/ +abbrev encRawVariable : CNF.PositiveExactOneCompile.Variable ℕ ↪ List Bool := + CNF.PositiveExactOneCompile.encVariable encNat + +/-- Inspect at most three list constructors to obtain the clause's bounded length tag. -/ +def boundedClauseLength (c : Clause ℕ) : Fin 4 := + if c.isEmpty then 0 else if c.tail.isEmpty then 1 else if c.tail.tail.isEmpty then 2 else 3 + +/-- The bounded length tag is exactly the first three source positions' presence count. -/ +theorem boundedClauseLength_eq (c : Clause ℕ) : + boundedClauseLength c = ⟨min c.length 3, by omega⟩ := by + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ + all_goals (apply Fin.ext; simp [boundedClauseLength]) + +/-- Native computation of the bounded presence tag examines three list heads. -/ +@[fun_prop] +theorem computableInPolyTime_boundedClauseLength : + ComputableInPolyTime boundedClauseLength encClause (encFinite (Fin 4)) := by + refine .of_tidy ?_ + unfold boundedClauseLength encClause + fun_prop + +/-- The finite tag consisting of the bounded length and first three polarities. -/ +def clauseTag (c : Clause ℕ) : Fin 4 × Bool × Bool × Bool := + (boundedClauseLength c, (c.getD 0 (0, true)).2, + (c.getD 1 (0, true)).2, (c.getD 2 (0, true)).2) + +private def profileOfTag (t : Fin 4 × Bool × Bool × Bool) : ClauseProfile := + clauseProfile ([(0, t.2.1), (1, t.2.2.1), (2, t.2.2.2)].take t.1.val) + +private theorem clauseProfile_eq_tag (c : Clause ℕ) : + clauseProfile c = profileOfTag (clauseTag c) := by + rcases c with _ | ⟨p, _ | ⟨q, _ | ⟨r, rest⟩⟩⟩ <;> rfl + +/-- Selecting the actual polarity profile is native polynomial-time. -/ +@[fun_prop] +theorem computableInPolyTime_clauseProfile : + ComputableInPolyTime clauseProfile encClause (encFinite ClauseProfile) := by + let tagEncoding := encPair (encFinite (Fin 4)) (encPair encBool (encPair encBool encBool)) + have hget (i : ℕ) : ComputableTidilyInPolyTime + (fun c : Clause ℕ => (c.getD i (0, true)).2) encClause encBool := by + have hg := (ComputableTidilyInPolyTime.listGetD + (encA := encPair encNat encBool) (encL := encClause) (0, true)).comp + ((ComputableTidilyInPolyTime.id' (encIn := encClause)).prod + (ComputableTidilyInPolyTime.const i)) + exact (ComputableTidilyInPolyTime.snd encNat encBool).comp hg + have htag : ComputableTidilyInPolyTime clauseTag encClause tagEncoding := + computableInPolyTime_boundedClauseLength.tidy.prod ((hget 0).prod ((hget 1).prod (hget 2))) + have hfinite : ComputableTidilyInPolyTime profileOfTag tagEncoding (encFinite ClauseProfile) := + .of_finite _ + have he : clauseProfile = profileOfTag ∘ clauseTag := funext clauseProfile_eq_tag + rw [he] + exact .of_tidy (hfinite.comp htag) + +/-- A cardinal-vector classifier uses only three binary-coordinate equality tests. -/ +@[fun_prop] +theorem computableInPolyTime_directionSide : + ComputableInPolyTime directionSide encPoint (encFinite (Fin 4)) := by + refine .of_tidy ?_ + unfold directionSide + fun_prop + +/-- Completing a partial source exit assignment is a fixed finite function. -/ +@[fun_prop] +theorem computableInPolyTime_choosePorts : + ComputableInPolyTime + (fun p : Fin 4 × Fin 4 × Fin 4 × Fin 4 => choosePorts p.1 p.2.1 p.2.2.1 p.2.2.2) + (encPair (encFinite (Fin 4)) (encPair (encFinite (Fin 4)) + (encPair (encFinite (Fin 4)) (encFinite (Fin 4))))) (encFinite PortAssignment) := + .of_tidy (.of_finite _) + +/-- A fixed profile/port pair computes its placed local variable-position list. -/ +@[fun_prop] +theorem computableInPolyTime_variablePositions : + ComputableInPolyTime (fun p : ClauseProfile × PortAssignment => + (collaredPrefixGeometry (profilePrefixGeometry p.1) p.2).variablePositions) + encProfilePorts encRoute := .of_tidy (.of_finite _) + +/-- A fixed profile/port pair computes its placed local clause-position list. -/ +@[fun_prop] +theorem computableInPolyTime_clausePositions : + ComputableInPolyTime (fun p : ClauseProfile × PortAssignment => + (collaredPrefixGeometry (profilePrefixGeometry p.1) p.2).clausePositions) + encProfilePorts encRoute := .of_tidy (.of_finite _) + +/-- A fixed profile/port pair computes its actual local incidence routes and indices. -/ +@[fun_prop] +theorem computableInPolyTime_incidenceRoutes : + ComputableInPolyTime (fun p : ClauseProfile × PortAssignment => + (collaredPrefixGeometry (profilePrefixGeometry p.1) p.2).incidenceRoutes) + encProfilePorts (encList encLocalIncidence) := .of_tidy (.of_finite _) + +/-- The source-variable list of a profile is a fixed finite lookup. -/ +@[fun_prop] +theorem computableInPolyTime_profileVariables : + ComputableInPolyTime profileVariables (encFinite ClauseProfile) (encList encRawVariable) := + .of_tidy (.of_finite _) + +/-- The exact logical formula of a profile is a fixed finite lookup. -/ +@[fun_prop] +theorem computableInPolyTime_profileFormula : + ComputableInPolyTime profileFormula (encFinite ClauseProfile) + (encList (encList (encPair encRawVariable encBool))) := .of_tidy (.of_finite _) + +/-- Classify a fixed profile variable, normalizing its source position to the three finite slots. +-/ +def variableRole (v : CNF.PositiveExactOneCompile.Variable ℕ) : Option (Fin 3) := + match v with + | .inl (.inl j) => some ⟨j % 3, Nat.mod_lt _ (by decide)⟩ + | _ => none + +/-- Source slot or internal-variable role of an indexed profile variable. Invalid indices +follow the actual drawing compiler's default source slot zero. -/ +def profileRole (p : ClauseProfile) (n : ℕ) : Option (Fin 3) := + variableRole ((profileVariables p).getD n (.inl (.inl 0))) + +/-- A fixed finite table of source-slot roles for the variables of each profile. -/ +def profileRoleTable (p : ClauseProfile) : List (Option (Fin 3)) := + (profileVariables p).map variableRole + +/-- The native role lookup agrees with the actual raw-variable classifier. -/ +theorem profileRole_eq_getD (p : ClauseProfile) (n : ℕ) : + profileRole p n = (profileRoleTable p).getD n (some 0) := by + unfold profileRole profileRoleTable + rw [List.getD_eq_getElem?_getD, List.getD_eq_getElem?_getD, List.getElem?_map] + cases (profileVariables p)[n]? <;> rfl + +/-- Every source-placeholder name occurring in a fixed profile is one of its three slots. -/ +theorem profileVariables_source_bound : ∀ p : ClauseProfile, ∀ v ∈ profileVariables p, + (match v with | .inl (.inl j) => j < 3 | _ => True) := by + have hall : ∀ p : ClauseProfile, (profileVariables p).all + (fun v => match v with | .inl (.inl j) => decide (j < 3) | _ => true) = true := by + decide +kernel + intro p v hv + have hh := List.all_eq_true.mp (hall p) v hv + rcases v with (j | k) | k + · exact of_decide_eq_true hh + · trivial + · trivial + +private theorem profileVariable_getD_source_bound (p : ClauseProfile) (n : ℕ) : + (match (profileVariables p).getD n (.inl (.inl 0)) with + | .inl (.inl j) => j < 3 | _ => True) := by + by_cases hn : n < (profileVariables p).length + · rw [List.getD_eq_getElem _ _ hn] + exact profileVariables_source_bound p _ (List.getElem_mem hn) + · rw [List.getD_eq_getElem?_getD, List.getElem?_eq_none (by omega)] + change (0 : ℕ) < 3 + decide + +/-- The finite role tag recovers the exact natural source index used by the drawing compiler. +-/ +theorem profileRole_map_val (p : ClauseProfile) (n : ℕ) : + (profileRole p n).map Fin.val = + (match (profileVariables p).getD n (.inl (.inl 0)) with + | .inl (.inl j) => some j | _ => none) := by + have hb := profileVariable_getD_source_bound p n + unfold profileRole variableRole + generalize he : (profileVariables p).getD n (.inl (.inl 0)) = v at hb ⊢ + cases v with + | inl v => + cases v with + | inl j => simpa only [Option.map_some] using congrArg some (Nat.mod_eq_of_lt hb) + | inr v => rfl + | inr v => rfl + +/-- The finite role table can be emitted in native polynomial time. -/ +@[fun_prop] +theorem computableInPolyTime_profileRoleTable : + ComputableInPolyTime profileRoleTable (encFinite ClauseProfile) + (encList (encFinite (Option (Fin 3)))) := .of_tidy (.of_finite _) + +/-- A role table lookup accepts any unary index, with no promise that it lies in the table. -/ +@[fun_prop] +theorem computableInPolyTime_profileRole : + ComputableInPolyTime (fun p : ClauseProfile × ℕ => profileRole p.1 p.2) + (encPair (encFinite ClauseProfile) encUnary) (encFinite (Option (Fin 3))) := by + have ht := computableInPolyTime_profileRoleTable.tidy.comp + (ComputableTidilyInPolyTime.fst (encFinite ClauseProfile) encUnary) + have hg := (ComputableTidilyInPolyTime.listGetD + (encA := encFinite (Option (Fin 3))) (encL := encList (encFinite (Option (Fin 3)))) + (some 0)).comp (ht.prod (ComputableTidilyInPolyTime.snd (encFinite ClauseProfile) encUnary)) + refine .of_tidy ?_ + simpa only [Function.comp_def, profileRole_eq_getD] using hg + +/-- Native computation of the selected collar follows from the three native source exits. +The dynamic geometric computation is supplied by the caller, independently of finite dispatch. +-/ +theorem computableInPolyTime_clausePorts_of_literalSide + (hside : ∀ j : ℕ, ComputableInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => literalSide p.1.1 p.1.2 p.2 j) + (encPair encInput encUnary) (encFinite (Fin 4))) : + ComputableInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => clausePorts p.1.1 p.1.2 p.2) + (encPair encInput encUnary) (encFinite PortAssignment) := by + have hc : ComputableTidilyInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => p.1.1.getD p.2 []) + (encPair encInput encUnary) encClause := by + unfold encInput encCNF encClause + fun_prop + have hn := computableInPolyTime_boundedClauseLength.tidy.comp hc + have ht := hn.prod ((hside 0).tidy.prod ((hside 1).tidy.prod (hside 2).tidy)) + have h := computableInPolyTime_choosePorts.tidy.comp ht + refine .of_tidy ?_ + simpa only [Function.comp_def, clausePorts, boundedClauseLength_eq] using h + +end Cslib.Complexity.SAT.ExactOne.DrawingFinitePolyTime diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingFits.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingFits.lean new file mode 100644 index 000000000..f889db0f6 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingFits.lean @@ -0,0 +1,260 @@ +/- +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.ExactOne.Drawing +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesCollar +import Mathlib.Data.List.GetD +import Mathlib.Tactic.Linarith + +/-! +# Constant-factor coordinate bounds for the actual positive drawing compiler + +Every emitted vertex and route point is bounded on the actual drawing lists. The source drawing +is dilated by `1080000`, and every local replacement lies in a side-`540000` square. The source +axis translation and the endpoint cuts fit within the same constant margin. These bounds do +not require geometric validity of the input drawing. + +## Main results + +* `positiveDrawing_fits`: an input rectangle of width `W` and height `H` yields an actual output + rectangle with sides `1080000 * (W + 1)` and `1080000 * (H + 1)`. + +## References + +The explicit coordinate and finite-output bounds are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +private def Rect (W H : ℕ) (p : Point) : Prop := + 0 ≤ p.1 ∧ p.1 ≤ W ∧ 0 ≤ p.2 ∧ p.2 ≤ H + +private theorem getD_property {A : Type*} (P : A → Prop) (xs : List A) (fallback : A) + (hf : P fallback) (hxs : ∀ x ∈ xs, P x) (i : ℕ) : P (xs.getD i fallback) := by + induction xs generalizing i with + | nil => simpa using hf + | cons a xs ih => + cases i with + | zero => exact hxs a List.mem_cons_self + | succ i => + exact ih (fun x hx => hxs x (List.mem_cons_of_mem _ hx)) i + +private theorem zero_rect (W H : ℕ) : Rect W H (0, 0) := by simp [Rect] + +private theorem input_position_bounds {d : Graph.OrthogonalDrawing} {W H : ℕ} + (hd : Graph.drawingFits d W H) (G : Graph) (v : ℕ) : + Rect W H (Graph.drawingPosition G d v) := + getD_property _ d.1 (0, 0) (zero_rect W H) hd.1 _ + +private theorem input_route_bounds {d : Graph.OrthogonalDrawing} {W H : ℕ} + (hd : Graph.drawingFits d W H) (i : ℕ) : + ∀ q ∈ d.2.getD i [], Rect W H q := + getD_property _ d.2 [] (by simp) hd.2 i + +private theorem sourceRoute_points_bounds {d : Graph.OrthogonalDrawing} {W H : ℕ} + (hd : Graph.drawingFits d W H) (G : Graph) (i : ℕ) : + ∀ q ∈ sourceRoute G d i, Rect W H q := by + unfold sourceRoute + split + · exact input_route_bounds hd i + · intro q hq + exact input_route_bounds hd i q (List.mem_reverse.mp hq) + +private theorem collar_outer_bounds (side : Fin 4) : + Rect 540000 540000 (collarOuterPort side) := by + unfold Rect + fin_cases side <;> decide +kernel + +private theorem collar_points_bounds : ∀ (s : PortAssignment) (i : Fin 3), + ∀ q ∈ (collarRoutes s).getD i [], Rect 540000 540000 q := by + unfold Rect + decide +kernel + +private theorem collarPoint_bounds (reflected : Bool) {q : Point} + (hq : Rect 180000 180000 q) : Rect 540000 540000 (collarPoint reflected q) := by + cases reflected <;> dsimp [Rect, collarPoint, collarOffset, mirrorPoint] at * <;> omega + +private theorem prefix_variable_bounds (p : ClauseProfile) : + ∀ q ∈ (profilePrefixGeometry p).variablePositions, Rect 180000 180000 q := by + intro q hq + obtain ⟨i, hi, rfl⟩ := List.mem_iff_getElem.mp hq + have h := profile_prefix_variable_bounds p ⟨i, hi⟩ + simpa only [Rect, Nat.cast_ofNat, LocalGeometry.variablePosition, + List.getD_eq_getElem _ _ hi] using h + +private theorem prefix_clause_bounds (p : ClauseProfile) : + ∀ q ∈ (profilePrefixGeometry p).clausePositions, Rect 180000 180000 q := by + intro q hq + obtain ⟨i, hi, rfl⟩ := List.mem_iff_getElem.mp hq + have h := profile_prefix_clause_bounds p ⟨i, hi⟩ + simpa only [Rect, Nat.cast_ofNat, LocalGeometry.clausePosition, + List.getD_eq_getElem _ _ hi] using h + +private theorem local_variable_bounds (p : ClauseProfile) (s : PortAssignment) (v : ℕ) : + Rect 540000 540000 + ((collaredPrefixGeometry (profilePrefixGeometry p) s).variablePosition v) := by + apply getD_property _ _ _ (zero_rect _ _) + intro q hq + obtain ⟨⟨a, i⟩, ha, rfl⟩ := List.mem_map.mp hq + split_ifs + · exact collar_outer_bounds _ + · exact collarPoint_bounds _ (prefix_variable_bounds p a (List.fst_mem_of_mem_zipIdx ha)) + +private theorem local_clause_bounds (p : ClauseProfile) (s : PortAssignment) (i : ℕ) : + Rect 540000 540000 ((collaredPrefixGeometry (profilePrefixGeometry p) s).clausePosition i) := by + apply getD_property _ _ _ (zero_rect _ _) + intro q hq + obtain ⟨a, ha, rfl⟩ := List.mem_map.mp hq + exact collarPoint_bounds _ (prefix_clause_bounds p a ha) + +private theorem collarRoute_points_bounds (reflected : Bool) {r : Route} + (hr : ∀ q ∈ r, Rect 180000 180000 q) : + ∀ q ∈ collarRoute reflected r, Rect 540000 540000 q := by + intro q hq + rw [collarRoute_eq_map] at hq + obtain ⟨a, ha, rfl⟩ := List.mem_map.mp hq + exact collarPoint_bounds _ (hr a ha) + +private theorem local_route_bounds (p : ClauseProfile) (s : PortAssignment) + {e : LocalIncidence} + (he : e ∈ (collaredPrefixGeometry (profilePrefixGeometry p) s).incidenceRoutes) : + ∀ q ∈ e.2.2, Rect 540000 540000 q := by + obtain ⟨f, hf, rfl⟩ := List.mem_map.mp he + dsimp only + split_ifs + · intro q hq + rw [collarPrefixRoute, List.mem_append] at hq + rcases hq with hq | hq + · exact collarRoute_points_bounds _ (profile_prefix_incidence_points_bounds p f hf) q hq + · exact collar_points_bounds s _ q (List.mem_reverse.mp (List.mem_of_mem_tail hq)) + · exact collarRoute_points_bounds _ (profile_prefix_incidence_points_bounds p f hf) + +private theorem placed_point_bounds {W H : ℕ} {a q : Point} + (ha : Rect W H a) (hq : Rect 540000 540000 q) : + Rect (1080000 * (W + 1)) (1080000 * (H + 1)) (positiveScale • a + q) := by + dsimp [Rect, positiveScale, Prod.smul_mk, Int.nsmul_eq_mul] at * + omega + +private theorem translation_bounds : Rect 540000 540000 positiveTranslation := by + unfold Rect + decide +kernel + +private theorem cut_offset_bounds (a b : Point) : + Rect 540000 540000 (positiveRadius • direction a b + positiveTranslation) := by + rcases direction_unit a b with h | h | h | h <;> rw [h] <;> + norm_num [Rect, positiveRadius, positiveTranslation, Prod.smul_mk] + +private theorem cutStart_points_bounds {W H : ℕ} {r : Route} + (hr : ∀ q ∈ r, Rect W H q) : + ∀ q ∈ cutStart positiveScale positiveRadius r, + Rect (1080000 * (W + 1)) (1080000 * (H + 1)) (q + positiveTranslation) := by + cases r with + | nil => simp [cutStart, dilateRoute] + | cons a r => + cases r with + | nil => + intro q hq + have he : q = positiveScale • a := by simpa [cutStart, dilateRoute] using hq + rw [he] + exact placed_point_bounds (hr a List.mem_cons_self) translation_bounds + | cons b r => + intro q hq + change q ∈ cutPoint positiveScale positiveRadius a b :: dilateRoute positiveScale (b :: r) + at hq + rcases List.mem_cons.mp hq with rfl | hq + · simpa only [cutPoint, add_assoc] using + placed_point_bounds (hr a List.mem_cons_self) (cut_offset_bounds a b) + · obtain ⟨x, hx, rfl⟩ := List.mem_map.mp hq + exact placed_point_bounds (hr x (List.mem_cons_of_mem _ hx)) translation_bounds + +/-- Every point of the actual inherited exterior route fits the enlarged source rectangle. -/ +theorem positiveExterior_points_fits {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} {W H : ℕ} + (hd : Graph.drawingFits d W H) (i j : ℕ) : + ∀ q ∈ positiveExterior φ d i j, + 0 ≤ q.1 ∧ q.1 ≤ 1080000 * (W + 1) ∧ + 0 ≤ q.2 ∧ q.2 ≤ 1080000 * (H + 1) := by + intro q hq + obtain ⟨x, hx, rfl⟩ := List.mem_map.mp hq + exact cutStart_points_bounds (sourceRoute_points_bounds hd _ _) x hx + +private theorem actual_variablePoint_bounds {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + {W H : ℕ} (hd : Graph.drawingFits d W H) (v : CNF.PositiveExactOneCompile.Variable ℕ) : + Rect (1080000 * (W + 1)) (1080000 * (H + 1)) (positiveVariablePoint φ d v) := by + apply getD_property _ _ _ (zero_rect _ _) + intro q hq + obtain ⟨⟨w, q⟩, htable, rfl⟩ := List.mem_map.mp hq + rcases List.mem_append.mp htable with hsource | haux + · obtain ⟨n, _, he⟩ := List.mem_map.mp hsource + have hq := congrArg Prod.snd he + rw [← hq] + exact placed_point_bounds (input_position_bounds hd _ _) translation_bounds + · obtain ⟨⟨c, i⟩, _, hentry⟩ := List.mem_flatMap.mp haux + obtain ⟨⟨w, j⟩, _, he⟩ := List.mem_filterMap.mp hentry + rcases w with (_ | _) | _ + · simp at he + all_goals + have heq := congrArg (Option.map Prod.snd) he + simp only [Option.map_some, Option.some.injEq] at heq + rw [← heq] + exact placed_point_bounds (input_position_bounds hd _ _) + (local_variable_bounds (clauseProfile c) _ _) + +private theorem actual_clausePoint_bounds {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + {W H : ℕ} (hd : Graph.drawingFits d W H) {pc : PositivePlacedClause} + (hpc : pc ∈ positivePlacedClauses φ d) : + Rect (1080000 * (W + 1)) (1080000 * (H + 1)) pc.2.1 := by + obtain ⟨⟨c, i⟩, _, hpc⟩ := List.mem_flatMap.mp hpc + obtain ⟨⟨a, j⟩, _, rfl⟩ := List.mem_map.mp hpc + exact placed_point_bounds (input_position_bounds hd _ _) + (local_clause_bounds (clauseProfile c) _ _) + +private theorem actual_incidenceRoute_bounds {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + {W H : ℕ} (hd : Graph.drawingFits d W H) (i : ℕ) (c : Clause ℕ) {e : LocalIncidence} + (he : e ∈ (clauseGeometry φ d i c).incidenceRoutes) : + ∀ q ∈ positiveIncidenceRoute φ d i c e, + Rect (1080000 * (W + 1)) (1080000 * (H + 1)) q := by + have hlocal : ∀ q ∈ translate (positiveBoxOrigin φ d i) e.2.2, + Rect (1080000 * (W + 1)) (1080000 * (H + 1)) q := by + intro q hq + obtain ⟨x, hx, rfl⟩ := List.mem_map.mp hq + rw [add_comm x] + exact placed_point_bounds (input_position_bounds hd _ _) + (local_route_bounds (clauseProfile c) _ he x hx) + unfold positiveIncidenceRoute + split + · intro q hq + rcases List.mem_append.mp hq with hq | hq + · exact hlocal q hq + · exact positiveExterior_points_fits hd _ _ q (List.mem_of_mem_tail hq) + · exact hlocal + +/-- The actual emitted drawing expands both source grid sides by only a fixed factor. This +holds for every finite input drawing, including malformed inputs handled by default lookups. -/ +theorem positiveDrawing_fits (φ : CNF ℕ) {d : Graph.OrthogonalDrawing} {W H : ℕ} + (hd : Graph.drawingFits d W H) : + Graph.drawingFits (positiveDrawing φ d) (1080000 * (W + 1)) (1080000 * (H + 1)) := by + constructor + · intro q hq + rcases List.mem_append.mp hq with hv | hc + · obtain ⟨v, _, rfl⟩ := List.mem_map.mp hv + exact actual_variablePoint_bounds hd v + · obtain ⟨pc, hpc, rfl⟩ := List.mem_map.mp hc + exact actual_clausePoint_bounds hd hpc + · intro r hr q hq + obtain ⟨pc, hpc, hr⟩ := List.mem_flatMap.mp hr + obtain ⟨⟨c, i⟩, _, hpc⟩ := List.mem_flatMap.mp hpc + obtain ⟨⟨a, j⟩, _, rfl⟩ := List.mem_map.mp hpc + obtain ⟨e, he, rfl⟩ := List.mem_map.mp hr + exact actual_incidenceRoute_bounds hd i c (List.mem_filter.mp he).1 q hq + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPolyTime.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPolyTime.lean new file mode 100644 index 000000000..011f1e43e --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPolyTime.lean @@ -0,0 +1,700 @@ +/- +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.ExactOne.Drawing +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingFinitePolyTime +public import Complexity.Computability.Complexity.Problems.Graph.OrthogonalDrawing.Encoding +public import Complexity.Logics.Propositional.CNF.ExactOne.PolyTime +public import Complexity.Computability.Complexity.Problems.SAT +import Complexity.Foundations.Geometry.Orthogonal.Macrocell +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.Option +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Sum + +/-! +# Native polynomial-time positive incidence drawing compilation + +Coordinates and original source labels use binary encodings. Clause, literal, and list indices +use unary counters bounded by the emitted lists. The finite profile and collar choices are fixed +independently of the input; there is no enumeration of the coordinate rectangle. + +## Main definitions + +* `DrawingNative.encInput`: the source formula and its supplied binary-coordinate drawing. + +## Main results + +Native certificates for source route normalization, constant-scale trimming, and the actual +positive drawing compiler. + +## References + +These are original native certificates for the explicit drawing construction. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne.DrawingNative + +open Cslib.Logic.PL Orthogonal Turing.MultiTapeTM + +/-- The encoded source formula and its supplied drawing. -/ +abbrev encInput : (CNF ℕ × Graph.OrthogonalDrawing) ↪ List Bool := + encPair encCNF Graph.encOrthogonalDrawing + +/-- Listed graphs retain binary vertex labels; edge positions use unary counters. -/ +abbrev encSourceIndex : ((Graph × Graph.OrthogonalDrawing) × ℕ) ↪ List Bool := + encPair (encPair encGraph Graph.encOrthogonalDrawing) encUnary + +attribute [local fun_prop] ComputableInPolyTime.tidy + +local instance : ListEncoding encPoint encRoute := + inferInstanceAs (ListEncoding encPoint (encList encPoint)) + +local instance : SizedListEncoding encPoint encRoute := + inferInstanceAs (SizedListEncoding encPoint (encList encPoint)) + +private theorem point_add : + 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 point_scale (k : ℕ) : + ComputableInPolyTime (fun p : Point => k • p) encPoint encPoint := by + refine .of_tidy ?_ + change ComputableTidilyInPolyTime + (fun p : Point => ((k : ℤ) * p.1, (k : ℤ) * p.2)) _ _ + unfold encPoint + fun_prop + +private theorem point_direction : + ComputableInPolyTime (fun p : Point × Point => direction p.1 p.2) + (encPair encPoint encPoint) encPoint := by + refine .of_tidy ?_ + unfold direction encPoint + fun_prop + +private theorem route_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 + +attribute [local fun_prop] point_add point_scale point_direction route_translate + +/-- Position lookup compares binary graph labels and scans only the declared vertex list. -/ +@[fun_prop] +theorem drawingPosition : + ComputableInPolyTime + (fun p : (Graph × Graph.OrthogonalDrawing) × ℕ => + Graph.drawingPosition p.1.1 p.1.2 p.2) + (encPair (encPair encGraph Graph.encOrthogonalDrawing) encNat) encPoint := by + refine .of_tidy ?_ + unfold Graph.drawingPosition encGraph Graph.encOrthogonalDrawing encRoute + fun_prop + +private theorem sourceEdge : + ComputableInPolyTime (fun p : (Graph × Graph.OrthogonalDrawing) × ℕ => + p.1.1.2.getD p.2 (0, 0)) encSourceIndex (encPair encNat encNat) := by + refine .of_tidy ?_ + unfold encSourceIndex encGraph + fun_prop + +/-- The clause endpoint of an indexed source edge is computed without expanding labels. -/ +@[fun_prop] +theorem sourceStart : + ComputableInPolyTime (fun p : (Graph × Graph.OrthogonalDrawing) × ℕ => + ExactOne.sourceStart p.1.1 p.1.2 p.2) encSourceIndex encPoint := by + have hp : ComputableTidilyInPolyTime + (fun p : (Graph × Graph.OrthogonalDrawing) × ℕ => + (p.1, (p.1.1.2.getD p.2 (0, 0)).2)) encSourceIndex + (encPair (encPair encGraph Graph.encOrthogonalDrawing) encNat) := + (ComputableTidilyInPolyTime.fst (encPair encGraph Graph.encOrthogonalDrawing) encUnary).prod + sourceEdge.tidy.snd' + simpa only [Function.comp_def, ExactOne.sourceStart] using + ComputableInPolyTime.of_tidy (drawingPosition.tidy.comp hp) + +/-- Reading and, when necessary, reversing an actual source route is polynomial-time. -/ +@[fun_prop] +theorem sourceRoute : + ComputableInPolyTime (fun p : (Graph × Graph.OrthogonalDrawing) × ℕ => + ExactOne.sourceRoute p.1.1 p.1.2 p.2) encSourceIndex encRoute := by + have hs := sourceStart.tidy + have hr : ComputableTidilyInPolyTime + (fun p : (Graph × Graph.OrthogonalDrawing) × ℕ => p.1.2.2.getD p.2 []) + encSourceIndex encRoute := by + unfold encSourceIndex Graph.encOrthogonalDrawing encRoute + fun_prop + have hhead := (ComputableTidilyInPolyTime.head? encPoint).comp hr + have hsome := (ComputableTidilyInPolyTime.optionSome encPoint).comp hs + exact .of_tidy ((hhead.decide_eq' hsome).ite_decide hr + ((ComputableTidilyInPolyTime.listReverse encPoint).comp hr)) + +private theorem routeSecond : + ComputableInPolyTime (fun r : Route => r.getD 1 (0, 0)) encRoute encPoint := by + have he : (fun r : Route => r.getD 1 (0, 0)) = (fun r => r.tail.headD (0, 0)) := by + funext r + rcases r with _ | ⟨a, _ | ⟨b, r⟩⟩ <;> rfl + rw [he] + refine .of_tidy ?_ + unfold encRoute + exact (ComputableTidilyInPolyTime.headD (encA := encPoint) + (encL := encList encPoint) (0, 0)).comp + (ComputableTidilyInPolyTime.tail (encA := encPoint) (encL := encList encPoint)) + +/-- The source exit direction needs only the first two listed route points. -/ +@[fun_prop] +theorem sourceDirection : + ComputableInPolyTime (fun p : (Graph × Graph.OrthogonalDrawing) × ℕ => + ExactOne.sourceDirection p.1.1 p.1.2 p.2) encSourceIndex encPoint := by + have hp : ComputableTidilyInPolyTime + (fun p : (Graph × Graph.OrthogonalDrawing) × ℕ => + (ExactOne.sourceStart p.1.1 p.1.2 p.2, + (ExactOne.sourceRoute p.1.1 p.1.2 p.2).getD 1 (0, 0))) + encSourceIndex (encPair encPoint encPoint) := + sourceStart.tidy.prod (routeSecond.tidy.comp sourceRoute.tidy) + simpa only [Function.comp_def, ExactOne.sourceDirection] using + ComputableInPolyTime.of_tidy (point_direction.tidy.comp hp) + +private theorem route_dilate (k : ℕ) : + ComputableInPolyTime (dilateRoute k) encRoute encRoute := by + refine .of_tidy ?_ + unfold dilateRoute encRoute + fun_prop + +private theorem point_cut (k radius : ℕ) : + ComputableInPolyTime (fun p : Point × Point => cutPoint k radius p.1 p.2) + (encPair encPoint encPoint) encPoint := by + refine .of_tidy ?_ + unfold cutPoint + fun_prop + +attribute [local fun_prop] route_dilate point_cut + +private theorem cutStart_eq (k radius : ℕ) (r : Route) : + cutStart k radius r = + if r.length < 2 then dilateRoute k r else + cutPoint k radius (r.getD 0 (0, 0)) (r.getD 1 (0, 0)) :: + dilateRoute k r.tail := by + rcases r with _ | ⟨a, _ | ⟨b, rest⟩⟩ <;> simp [cutStart, dilateRoute] + +/-- Constant-scale endpoint trimming scans the supplied route and computes two point images. -/ +@[fun_prop] +theorem cutStart (k radius : ℕ) : + ComputableInPolyTime (Orthogonal.cutStart k radius) encRoute encRoute := by + have hget (i : ℕ) := + (ComputableTidilyInPolyTime.listGetD (encA := encPoint) (encL := encRoute) (0, 0)).comp + ((ComputableTidilyInPolyTime.id' (encIn := encRoute)).prod + (ComputableTidilyInPolyTime.const i)) + have hcut := (point_cut k radius).tidy.comp ((hget 0).prod (hget 1)) + have htail := (route_dilate k).tidy.comp + (ComputableTidilyInPolyTime.tail (encA := encPoint)) + have hlen := ComputableTidilyInPolyTime.length encPoint (encL := encRoute) + have htest := ComputableTidilyInPolyTime.unary_lt.comp + (hlen.prod (ComputableTidilyInPolyTime.const 2)) + have h := htest.ite_decide (route_dilate k).tidy (hcut.cons' htail) + apply ComputableInPolyTime.of_tidy + change ComputableTidilyInPolyTime (fun r => Orthogonal.cutStart k radius r) _ _ + simpa only [cutStart_eq, Function.comp_def] using h + +/-- The trimmed exterior of each indexed source edge is polynomial-time. -/ +@[fun_prop] +theorem exteriorRoute (k radius : ℕ) : + ComputableInPolyTime (fun p : (Graph × Graph.OrthogonalDrawing) × ℕ => + ExactOne.exteriorRoute k radius p.1.1 p.1.2 p.2) encSourceIndex encRoute := + (cutStart k radius).comp sourceRoute + +/-- Clause and literal indices of a source occurrence are unary; original labels stay binary. -/ +@[fun_prop] +theorem sourceEdgeIndex : + ComputableInPolyTime (fun p : CNF ℕ × (ℕ × ℕ) => + ExactOne.sourceEdgeIndex p.1 p.2.1 p.2.2) + (encPair encCNF (encPair encUnary encUnary)) encUnary := by + refine .of_tidy ?_ + apply ComputableTidilyInPolyTime.idxOfWithInput + · exact (Cslib.Logic.PL.computableInPolyTime_occurrences encNat).tidy.comp + (ComputableTidilyInPolyTime.fst encCNF (encPair encUnary encUnary)) + · change ComputableTidilyInPolyTime + (fun p : CNF ℕ × (ℕ × ℕ) => sourceOccurrence p.1 p.2.1 p.2.2) + (encPair encCNF (encPair encUnary encUnary)) (Cslib.Logic.PL.encOccurrence encNat) + have hφ := ComputableTidilyInPolyTime.fst encCNF (encPair encUnary encUnary) + have hij := ComputableTidilyInPolyTime.snd encCNF (encPair encUnary encUnary) + have hi := (ComputableTidilyInPolyTime.fst encUnary encUnary).comp hij + have hj := (ComputableTidilyInPolyTime.snd encUnary encUnary).comp hij + have hc : ComputableTidilyInPolyTime + (fun p : CNF ℕ × (ℕ × ℕ) => p.1.getD p.2.1 []) + (encPair encCNF (encPair encUnary encUnary)) DrawingFinitePolyTime.encClause := + (ComputableTidilyInPolyTime.listGetD (encA := DrawingFinitePolyTime.encClause) + (encL := encList DrawingFinitePolyTime.encClause) []).comp (hφ.prod hi) + have hl : ComputableTidilyInPolyTime + (fun p : CNF ℕ × (ℕ × ℕ) => (p.1.getD p.2.1 []).getD p.2.2 (0, true)) + (encPair encCNF (encPair encUnary encUnary)) (encPair encNat encBool) := by + have hdrop := (ComputableTidilyInPolyTime.dropUnary + (encA := encPair encNat encBool) (encL := encList (encPair encNat encBool))).comp + (hc.prod hj) + have h := (ComputableTidilyInPolyTime.headD (encA := encPair encNat encBool) + (encL := encList (encPair encNat encBool)) (0, true)).comp hdrop + simpa only [Function.comp_def, List.headD_eq_head?_getD, List.head?_drop, + List.getD_eq_getElem?_getD] using h + simpa only [Function.comp_def, sourceOccurrence, Cslib.Logic.PL.encOccurrence] using + hi.prod (hj.prod hl) + +private theorem graphDrawing : + ComputableInPolyTime + (fun p : CNF ℕ × Graph.OrthogonalDrawing => (SAT.incidenceGraph p.1, p.2)) + encInput (encPair encGraph Graph.encOrthogonalDrawing) := by + have hg := (SAT.computableInPolyTime_incidenceGraph encNat).tidy + refine .of_tidy ?_ + unfold encInput encCNF at * + fun_prop + +private theorem sourceIndex (j : ℕ) : + ComputableInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => + ((SAT.incidenceGraph p.1.1, p.1.2), ExactOne.sourceEdgeIndex p.1.1 p.2 j)) + (encPair encInput encUnary) encSourceIndex := by + have hg := graphDrawing.tidy.comp (ComputableTidilyInPolyTime.fst encInput encUnary) + have hp : ComputableTidilyInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => (p.1.1, (p.2, j))) + (encPair encInput encUnary) (encPair encCNF (encPair encUnary encUnary)) := by + unfold encInput + fun_prop + have hx : ComputableTidilyInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => + ExactOne.sourceEdgeIndex p.1.1 p.2 j) (encPair encInput encUnary) encUnary := by + simpa only [Function.comp_def] using sourceEdgeIndex.tidy.comp hp + simpa only [Function.comp_def] using ComputableInPolyTime.of_tidy (hg.prod hx) + +/-- Each actual source exit has a native finite cardinal-direction tag. -/ +@[fun_prop] +theorem literalSide (j : ℕ) : + ComputableInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => ExactOne.literalSide p.1.1 p.1.2 p.2 j) + (encPair encInput encUnary) (encFinite (Fin 4)) := by + have h := DrawingFinitePolyTime.computableInPolyTime_directionSide.comp + (sourceDirection.comp (sourceIndex j)) + simpa only [Function.comp_def, ExactOne.literalSide] using h + +/-- Complete the present exits to one of the fixed ordered collars in native polynomial time. -/ +@[fun_prop] +theorem clausePorts : + ComputableInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => ExactOne.clausePorts p.1.1 p.1.2 p.2) + (encPair encInput encUnary) (encFinite PortAssignment) := + DrawingFinitePolyTime.computableInPolyTime_clausePorts_of_literalSide literalSide + +/-- The lower-left box origin uses the source's actual clause position and a constant dilation. -/ +@[fun_prop] +theorem positiveBoxOrigin : + ComputableInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => + ExactOne.positiveBoxOrigin p.1.1 p.1.2 p.2) + (encPair encInput encUnary) encPoint := by + have hd := (CNF.computableInPolyTime_distinctVars encNat).tidy + have hn : ComputableTidilyInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => p.1.1.distinctVars.length + p.2) + (encPair encInput encUnary) encUnary := by + unfold encInput encCNF at * + fun_prop + have hb := ComputableTidilyInPolyTime.unary_to_binary.comp hn + have hg := graphDrawing.tidy.comp (ComputableTidilyInPolyTime.fst encInput encUnary) + have hp := drawingPosition.tidy.comp (hg.prod hb) + have h := (point_scale positiveScale).tidy.comp hp + simpa only [Function.comp_def, ExactOne.positiveBoxOrigin] using ComputableInPolyTime.of_tidy h + +/-- The actual dilated and trimmed inherited route is emitted without searching the plane. -/ +@[fun_prop] +theorem positiveExterior (j : ℕ) : + ComputableInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => + ExactOne.positiveExterior p.1.1 p.1.2 p.2 j) + (encPair encInput encUnary) encRoute := by + have he := (exteriorRoute positiveScale positiveRadius).tidy.comp (sourceIndex j).tidy + have h := route_translate.tidy.comp + ((ComputableTidilyInPolyTime.const positiveTranslation).prod he) + simpa only [Function.comp_def, ExactOne.positiveExterior] using ComputableInPolyTime.of_tidy h + +private theorem take_zipIdx {A : Type*} (xs : List A) (i start : ℕ) : + (xs.zipIdx start).take i = (xs.take i).zipIdx start := by + induction xs generalizing i start with + | nil => simp + | cons a xs ih => cases i <;> simp [List.zipIdx_cons, ih] + +private theorem signedOffset_eq_length (φ : CNF ℕ) (i : ℕ) : + CNF.PositiveExactOneCompile.signedOffset φ i = (CNF.FromSAT.formula (φ.take i)).length := by + simp only [CNF.PositiveExactOneCompile.signedOffset, CNF.PositiveExactOne.blockOffset, + ← List.map_take, take_zipIdx, List.map_map, Function.comp_def, CNF.FromSAT.formula, + List.length_flatMap] + +/-- Prefix offsets are computed as lengths of the already certified signed clause compiler. -/ +@[fun_prop] +theorem signedOffset : + ComputableInPolyTime (fun p : CNF ℕ × ℕ => CNF.PositiveExactOneCompile.signedOffset p.1 p.2) + (encPair encCNF encUnary) encUnary := by + have ht : ComputableTidilyInPolyTime (fun p : CNF ℕ × ℕ => p.1.take p.2) + (encPair encCNF encUnary) encCNF := by + unfold encCNF + fun_prop + have hf := (CNF.FromSAT.computableInPolyTime_formula encNat).tidy.comp ht + have hl := (ComputableTidilyInPolyTime.length + (encList (encPair (CNF.FromSAT.encVariable encNat) encBool))).comp hf + simpa only [Function.comp_def, signedOffset_eq_length] using ComputableInPolyTime.of_tidy hl + +private abbrev BlockInput := (CNF ℕ × Graph.OrthogonalDrawing) × (ℕ × Clause ℕ) + +private abbrev encBlockInput : BlockInput ↪ List Bool := + encPair encInput (encPair encUnary DrawingFinitePolyTime.encClause) + +private abbrev encRawVariable := DrawingFinitePolyTime.encRawVariable + +private abbrev encPlacedClause : PositivePlacedClause ↪ List Bool := + encPair (encList (encPair encRawVariable encBool)) (encPair encPoint (encList encRoute)) + +private theorem blockIndex : + ComputableTidilyInPolyTime (fun p : BlockInput => (p.1, p.2.1)) + encBlockInput (encPair encInput encUnary) := by + unfold encBlockInput + fun_prop + +private theorem blockOrigin : + ComputableTidilyInPolyTime + (fun p : BlockInput => ExactOne.positiveBoxOrigin p.1.1 p.1.2 p.2.1) + encBlockInput encPoint := by + simpa only [Function.comp_def] using positiveBoxOrigin.tidy.comp blockIndex + +private theorem blockSignedOffset : + ComputableTidilyInPolyTime + (fun p : BlockInput => CNF.PositiveExactOneCompile.signedOffset p.1.1 p.2.1) + encBlockInput encUnary := by + have hp : ComputableTidilyInPolyTime (fun p : BlockInput => (p.1.1, p.2.1)) + encBlockInput (encPair encCNF encUnary) := by + unfold encBlockInput encInput + fun_prop + simpa only [Function.comp_def] using signedOffset.tidy.comp hp + +private theorem blockVariable (v : CNF.PositiveExactOneCompile.Variable ℕ) : + ComputableTidilyInPolyTime (fun p : BlockInput => + profileVariableMap p.2.1 (CNF.PositiveExactOneCompile.signedOffset p.1.1 p.2.1) p.2.2 v) + encBlockInput encRawVariable := by + rcases v with (n | ⟨i, k⟩) | ⟨⟨i, j⟩, k⟩ + · have hc : ComputableTidilyInPolyTime (fun p : BlockInput => p.2.2) + encBlockInput DrawingFinitePolyTime.encClause := by + unfold encBlockInput + fun_prop + have hd := (ComputableTidilyInPolyTime.dropUnary (encA := encPair encNat encBool) + (encL := encList (encPair encNat encBool))).comp + (hc.prod (ComputableTidilyInPolyTime.const n)) + have h := (ComputableTidilyInPolyTime.headD (encA := encPair encNat encBool) + (encL := encList (encPair encNat encBool)) (0, true)).comp hd + have hn : ComputableTidilyInPolyTime (fun p : BlockInput => (p.2.2.getD n (0, true)).1) + encBlockInput encNat := by + simpa only [Function.comp_def, List.headD_eq_head?_getD, List.head?_drop, + List.getD_eq_getElem?_getD] using h.fst' + unfold profileVariableMap encRawVariable DrawingFinitePolyTime.encRawVariable + CNF.PositiveExactOneCompile.encVariable CNF.PositiveExactOne.encVariable + CNF.FromSAT.encVariable + fun_prop + · unfold profileVariableMap encRawVariable DrawingFinitePolyTime.encRawVariable + CNF.PositiveExactOneCompile.encVariable CNF.PositiveExactOne.encVariable + CNF.FromSAT.encVariable encBlockInput + fun_prop + · have ho := blockSignedOffset + unfold profileVariableMap encRawVariable DrawingFinitePolyTime.encRawVariable + CNF.PositiveExactOneCompile.encVariable CNF.PositiveExactOne.encVariable + CNF.FromSAT.encVariable + fun_prop + +private def incidenceFor (profile : ClauseProfile) (e : LocalIncidence) (p : BlockInput) : Route := + let stem := translate (ExactOne.positiveBoxOrigin p.1.1 p.1.2 p.2.1) e.2.2 + match (profileVariables profile).getD e.2.1 (.inl (.inl 0)) with + | .inl (.inl j) => stem ++ (ExactOne.positiveExterior p.1.1 p.1.2 p.2.1 j).tail + | _ => stem + +private theorem incidenceFor_poly (profile : ClauseProfile) (e : LocalIncidence) : + ComputableTidilyInPolyTime (incidenceFor profile e) encBlockInput encRoute := by + have hs := route_translate.tidy.comp + (blockOrigin.prod (ComputableTidilyInPolyTime.const e.2.2)) + have he (j : ℕ) := (positiveExterior j).tidy.comp blockIndex + unfold incidenceFor + rcases (profileVariables profile).getD e.2.1 (.inl (.inl 0)) with (j | v) | v + · exact hs.listAppend' ((ComputableTidilyInPolyTime.tail (encA := encPoint)).comp (he j)) + · exact hs + · exact hs + +private theorem blockTag : + ComputableTidilyInPolyTime (fun p : BlockInput => + (clauseProfile p.2.2, ExactOne.clausePorts p.1.1 p.1.2 p.2.1)) + encBlockInput DrawingFinitePolyTime.encProfilePorts := by + have hc : ComputableTidilyInPolyTime (fun p : BlockInput => p.2.2) + encBlockInput DrawingFinitePolyTime.encClause := by + unfold encBlockInput + fun_prop + have hp : ComputableTidilyInPolyTime (fun p : BlockInput => clauseProfile p.2.2) + encBlockInput (encFinite ClauseProfile) := by + simpa only [Function.comp_def] using + DrawingFinitePolyTime.computableInPolyTime_clauseProfile.tidy.comp hc + have hs : ComputableTidilyInPolyTime + (fun p : BlockInput => ExactOne.clausePorts p.1.1 p.1.2 p.2.1) + encBlockInput (encFinite PortAssignment) := by + simpa only [Function.comp_def] using clausePorts.tidy.comp blockIndex + exact hp.prod hs + +private def placedBlockFor (tag : ClauseProfile × PortAssignment) (p : BlockInput) : + List PositivePlacedClause := + let g := collaredPrefixGeometry (profilePrefixGeometry tag.1) tag.2 + (profileFormula tag.1).zipIdx.map fun a => + (a.1.relabel (profileVariableMap p.2.1 + (CNF.PositiveExactOneCompile.signedOffset p.1.1 p.2.1) p.2.2), + ExactOne.positiveBoxOrigin p.1.1 p.1.2 p.2.1 + g.clausePosition a.2, + (g.incidenceRoutes.filter fun e => e.1 == a.2).map fun e => incidenceFor tag.1 e p) + +private theorem placedBlockFor_poly (tag : ClauseProfile × PortAssignment) : + ComputableTidilyInPolyTime (placedBlockFor tag) encBlockInput (encList encPlacedClause) := by + unfold placedBlockFor + apply ComputableTidilyInPolyTime.mapConst + intro a _ + have hc : ComputableTidilyInPolyTime (fun p : BlockInput => a.1.relabel + (profileVariableMap p.2.1 (CNF.PositiveExactOneCompile.signedOffset p.1.1 p.2.1) p.2.2)) + encBlockInput (encList (encPair encRawVariable encBool)) := by + unfold Clause.relabel + apply ComputableTidilyInPolyTime.mapConst + intro l _ + exact (blockVariable l.1).prod (ComputableTidilyInPolyTime.const l.2) + have hp := point_add.tidy.comp (blockOrigin.prod (ComputableTidilyInPolyTime.const + ((collaredPrefixGeometry (profilePrefixGeometry tag.1) tag.2).clausePosition a.2))) + have hr := ComputableTidilyInPolyTime.mapConst + ((collaredPrefixGeometry (profilePrefixGeometry tag.1) tag.2).incidenceRoutes.filter + fun e => e.1 == a.2) (fun e _ => incidenceFor_poly tag.1 e) + exact hc.prod (hp.prod hr) + +private theorem positiveClauseBlock : + ComputableTidilyInPolyTime (fun p : BlockInput => + ExactOne.positiveClauseBlock p.1.1 p.1.2 p.2.1 p.2.2) + encBlockInput (encList encPlacedClause) := by + convert ComputableTidilyInPolyTime.finite_cases blockTag placedBlockFor_poly using 1 + funext p + rfl + +private def auxiliaryBlockFor (tag : ClauseProfile × PortAssignment) (p : BlockInput) : + List (CNF.PositiveExactOneCompile.Variable ℕ × Point) := + (profileVariables tag.1).zipIdx.filterMap fun v => + match v.1 with + | .inl (.inl _) => none + | _ => some (profileVariableMap p.2.1 + (CNF.PositiveExactOneCompile.signedOffset p.1.1 p.2.1) p.2.2 v.1, + ExactOne.positiveBoxOrigin p.1.1 p.1.2 p.2.1 + + (collaredPrefixGeometry (profilePrefixGeometry tag.1) tag.2).variablePosition v.2) + +private theorem auxiliaryBlockFor_poly (tag : ClauseProfile × PortAssignment) : + ComputableTidilyInPolyTime (auxiliaryBlockFor tag) encBlockInput + (encList (encPair encRawVariable encPoint)) := by + have hlist (xs : List (CNF.PositiveExactOneCompile.Variable ℕ × ℕ)) : + ComputableTidilyInPolyTime (fun p : BlockInput => xs.filterMap fun v => + match v.1 with + | .inl (.inl _) => none + | _ => some (profileVariableMap p.2.1 + (CNF.PositiveExactOneCompile.signedOffset p.1.1 p.2.1) p.2.2 v.1, + ExactOne.positiveBoxOrigin p.1.1 p.1.2 p.2.1 + + (collaredPrefixGeometry (profilePrefixGeometry tag.1) tag.2).variablePosition v.2)) + encBlockInput (encList (encPair encRawVariable encPoint)) := by + induction xs with + | nil => exact ComputableTidilyInPolyTime.const [] + | cons v xs ih => + have hp := point_add.tidy.comp (blockOrigin.prod (ComputableTidilyInPolyTime.const + ((collaredPrefixGeometry (profilePrefixGeometry tag.1) tag.2).variablePosition v.2))) + rcases v with ⟨(j | a) | a, n⟩ + · exact ih + · exact ((blockVariable _).prod hp).cons' ih + · exact ((blockVariable _).prod hp).cons' ih + exact hlist _ + +private theorem positiveAuxiliaryBlock : + ComputableTidilyInPolyTime (fun p : BlockInput => + ExactOne.positiveAuxiliaryBlock p.1.1 p.1.2 p.2.1 p.2.2) + encBlockInput (encList (encPair encRawVariable encPoint)) := by + convert ComputableTidilyInPolyTime.finite_cases blockTag auxiliaryBlockFor_poly using 1 + funext p + rfl + +private theorem sourceClauses : + ComputableTidilyInPolyTime (fun p : CNF ℕ × Graph.OrthogonalDrawing => p.1.zipIdx) + encInput (encList (encPair DrawingFinitePolyTime.encClause encUnary)) := by + unfold encInput encCNF DrawingFinitePolyTime.encClause + fun_prop + +private theorem clauseFrame : + ComputableTidilyInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × (Clause ℕ × ℕ) => + (p.1, (p.2.2, p.2.1))) + (encPair encInput (encPair DrawingFinitePolyTime.encClause encUnary)) encBlockInput := by + unfold encBlockInput + fun_prop + +private theorem positivePlacedClauses : + ComputableTidilyInPolyTime + (fun p : CNF ℕ × Graph.OrthogonalDrawing => ExactOne.positivePlacedClauses p.1 p.2) + encInput (encList encPlacedClause) := by + have hf := positiveClauseBlock.comp clauseFrame + simpa only [Function.comp_def, ExactOne.positivePlacedClauses] using + ComputableTidilyInPolyTime.flatMapWithInput sourceClauses hf + +private theorem positiveAuxiliaryPositions : + ComputableTidilyInPolyTime + (fun p : CNF ℕ × Graph.OrthogonalDrawing => ExactOne.positiveAuxiliaryPositions p.1 p.2) + encInput (encList (encPair encRawVariable encPoint)) := by + have hf : ComputableTidilyInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × (Clause ℕ × ℕ) => + ExactOne.positiveAuxiliaryBlock p.1.1 p.1.2 p.2.2 p.2.1) + (encPair encInput (encPair DrawingFinitePolyTime.encClause encUnary)) + (encList (encPair encRawVariable encPoint)) := by + simpa only [Function.comp_def] using positiveAuxiliaryBlock.comp clauseFrame + unfold ExactOne.positiveAuxiliaryPositions + exact ComputableTidilyInPolyTime.flatMapWithInput + (f := fun (p : CNF ℕ × Graph.OrthogonalDrawing) (c : Clause ℕ × ℕ) => + ExactOne.positiveAuxiliaryBlock p.1 p.2 c.2 c.1) + (encA := encPair DrawingFinitePolyTime.encClause encUnary) + (encB := encPair encRawVariable encPoint) (encIn := encInput) sourceClauses hf + +private theorem sourceVariables : + ComputableTidilyInPolyTime (fun p : CNF ℕ × Graph.OrthogonalDrawing => p.1.distinctVars) + encInput (encList encNat) := + (CNF.computableInPolyTime_distinctVars encNat).tidy.comp + (ComputableTidilyInPolyTime.fst encCNF Graph.encOrthogonalDrawing) + +private theorem sourceVariablePosition : + ComputableTidilyInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => positiveScale • + Graph.drawingPosition (SAT.incidenceGraph p.1.1) p.1.2 (p.1.1.distinctVars.idxOf p.2) + + positiveTranslation) (encPair encInput encNat) encPoint := by + have hi : ComputableTidilyInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => p.1.1.distinctVars.idxOf p.2) + (encPair encInput encNat) encUnary := + ComputableTidilyInPolyTime.idxOfWithInput + (sourceVariables.comp (ComputableTidilyInPolyTime.fst encInput encNat)) + (ComputableTidilyInPolyTime.snd encInput encNat) + have hb := ComputableTidilyInPolyTime.unary_to_binary.comp hi + have hg := graphDrawing.tidy.comp (ComputableTidilyInPolyTime.fst encInput encNat) + have hp := drawingPosition.tidy.comp (hg.prod hb) + have h := point_add.tidy.comp (((point_scale positiveScale).tidy.comp hp).prod + (ComputableTidilyInPolyTime.const positiveTranslation)) + simpa only [Function.comp_def] using h + +private theorem positiveSourcePositions : + ComputableTidilyInPolyTime + (fun p : CNF ℕ × Graph.OrthogonalDrawing => ExactOne.positiveSourcePositions p.1 p.2) + encInput (encList (encPair encRawVariable encPoint)) := by + have hv : ComputableTidilyInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × ℕ => + (Sum.inl (Sum.inl p.2) : CNF.PositiveExactOneCompile.Variable ℕ)) + (encPair encInput encNat) encRawVariable := by + unfold encRawVariable DrawingFinitePolyTime.encRawVariable + CNF.PositiveExactOneCompile.encVariable CNF.PositiveExactOne.encVariable + CNF.FromSAT.encVariable + fun_prop + simpa only [ExactOne.positiveSourcePositions] using + ComputableTidilyInPolyTime.mapWithInput sourceVariables (hv.prod sourceVariablePosition) + +private theorem positivePositionTable : + ComputableTidilyInPolyTime + (fun p : CNF ℕ × Graph.OrthogonalDrawing => ExactOne.positivePositionTable p.1 p.2) + encInput (encList (encPair encRawVariable encPoint)) := + positiveSourcePositions.listAppend' positiveAuxiliaryPositions + +private theorem positiveVariablePoint : + ComputableTidilyInPolyTime + (fun p : (CNF ℕ × Graph.OrthogonalDrawing) × CNF.PositiveExactOneCompile.Variable ℕ => + ExactOne.positiveVariablePoint p.1.1 p.1.2 p.2) + (encPair encInput encRawVariable) encPoint := by + have ht := positivePositionTable.comp (ComputableTidilyInPolyTime.fst encInput encRawVariable) + have hk := (ComputableTidilyInPolyTime.map + (ComputableTidilyInPolyTime.fst encRawVariable encPoint)).comp ht + have hp := (ComputableTidilyInPolyTime.map + (ComputableTidilyInPolyTime.snd encRawVariable encPoint)).comp ht + have hi := ComputableTidilyInPolyTime.idxOfWithInput hk + (ComputableTidilyInPolyTime.snd encInput encRawVariable) + have eqIdx (xs : List (CNF.PositiveExactOneCompile.Variable ℕ)) + (v : CNF.PositiveExactOneCompile.Variable ℕ) : + @List.idxOf _ instBEqOfDecidableEq v xs = xs.idxOf v := by + induction xs with + | nil => rfl + | cons a xs ih => simp only [List.idxOf_cons, beq_iff_eq, ih] + have hd := (ComputableTidilyInPolyTime.dropUnary (encA := encPoint)).comp (hp.prod hi) + have h := (ComputableTidilyInPolyTime.headD (encA := encPoint) (0, 0)).comp hd + simpa only [Function.comp_def, ExactOne.positiveVariablePoint, List.headD_eq_head?_getD, + List.head?_drop, List.getD_eq_getElem?_getD, eqIdx] using h + +private theorem rawVariables : + ComputableTidilyInPolyTime + (fun p : CNF ℕ × Graph.OrthogonalDrawing => + (CNF.PositiveExactOneCompile.rawFormula p.1).distinctVars) + encInput (encList encRawVariable) := by + have hf : ComputableTidilyInPolyTime + (fun p : CNF ℕ × Graph.OrthogonalDrawing => CNF.PositiveExactOneCompile.rawFormula p.1) + encInput (encList (encList (encPair encRawVariable encBool))) := by + simpa only [Function.comp_def] using + (CNF.PositiveExactOneCompile.computableInPolyTime_rawFormula encNat).tidy.comp + (ComputableTidilyInPolyTime.fst encCNF Graph.encOrthogonalDrawing) + simpa only [Function.comp_def] using + (CNF.computableInPolyTime_distinctVars encRawVariable).tidy.comp hf + +private theorem outputVariables : + ComputableTidilyInPolyTime + (fun p : CNF ℕ × Graph.OrthogonalDrawing => + (CNF.PositiveExactOneCompile.rawFormula p.1).distinctVars.map + (ExactOne.positiveVariablePoint p.1 p.2)) encInput (encList encPoint) := + ComputableTidilyInPolyTime.mapWithInput + (encA := encRawVariable) (encB := encPoint) (encIn := encInput) + (f := fun p v => ExactOne.positiveVariablePoint p.1 p.2 v) rawVariables positiveVariablePoint + +private theorem outputClauses : + ComputableTidilyInPolyTime + (fun p : CNF ℕ × Graph.OrthogonalDrawing => + (ExactOne.positivePlacedClauses p.1 p.2).map fun c => c.2.1) + encInput (encList encPoint) := by + have h := (ComputableTidilyInPolyTime.map + ((ComputableTidilyInPolyTime.snd (encList (encPair encRawVariable encBool)) + (encPair encPoint (encList encRoute))).fst')).comp positivePlacedClauses + simpa only [Function.comp_def] using h + +private theorem outputRoutes : + ComputableTidilyInPolyTime + (fun p : CNF ℕ × Graph.OrthogonalDrawing => + (ExactOne.positivePlacedClauses p.1 p.2).flatMap fun c => c.2.2) + encInput (encList encRoute) := by + have hm := (ComputableTidilyInPolyTime.map + ((ComputableTidilyInPolyTime.snd (encList (encPair encRawVariable encBool)) + (encPair encPoint (encList encRoute))).snd')).comp positivePlacedClauses + have h := (ComputableTidilyInPolyTime.flatten encRoute).comp hm + simpa only [Function.comp_def, List.flatten_eq_flatMap, List.flatMap_map, id_eq] using h + +/-- The actual emitted drawing is natively polynomial-time on binary coordinates and labels. +The proof scans the explicit lists and dispatches among fixed local templates. -/ +@[fun_prop] +theorem computableInPolyTime_positiveDrawing : + ComputableInPolyTime + (fun p : CNF ℕ × Graph.OrthogonalDrawing => ExactOne.positiveDrawing p.1 p.2) + encInput Graph.encOrthogonalDrawing := by + have h := (outputVariables.listAppend' outputClauses).prod outputRoutes + apply ComputableInPolyTime.of_tidy + simpa only [Function.comp_def, ExactOne.positiveDrawing, Graph.encOrthogonalDrawing, + encRoute] using h + +end Cslib.Complexity.SAT.ExactOne.DrawingNative diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPorts.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPorts.lean new file mode 100644 index 000000000..c54bcb16d --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPorts.lean @@ -0,0 +1,230 @@ +/- +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.ExactOne.DrawingSource +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryCollarData +public import Complexity.Computability.Complexity.Problems.SAT.Incidence +import Mathlib.Tactic.FinCases +import Mathlib.Data.List.GetD + +/-! +# Cardinal port selection for actual source incidences + +The side convention agrees with the finite collar tables. Any zero, one, two, or three distinct +prescribed source exits extends to one of the twenty-four ordered three-port assignments. + +## Main definitions + +* `directionSide`, `sideDirection`: cardinal-vector and collar-side conversion. +* `choosePorts`: finite deterministic completion of a partial source exit assignment. + +## Main results + +* `sideDirection_directionSide`: cardinal directions are preserved exactly. +* `choosePorts_spec`: the selected ports are distinct and retain every present source exit. + +## References + +These finite collar-selection and geometric indexing proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- Collar sides are numbered left, right, bottom, top. -/ +def sideDirection (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) + +/-- Classify a cardinal direction using the collar side convention. -/ +def directionSide (v : Point) : Fin 4 := + if v = (-1, 0) then 0 else if v = (1, 0) then 1 else if v = (0, -1) then 2 else 3 + +/-- Converting a unit direction to its collar side and back recovers it. -/ +theorem sideDirection_directionSide {v : Point} (hv : UnitVector v) : + sideDirection (directionSide v) = v := by + rcases hv with rfl | rfl | rfl | rfl <;> decide + +/-- Distinct unit vectors are classified as distinct collar sides. -/ +theorem directionSide_injective {v w : Point} (hv : UnitVector v) (hw : UnitVector w) + (he : directionSide v = directionSide w) : v = w := by + rw [← sideDirection_directionSide hv, ← sideDirection_directionSide hw, he] + +/-- Check the source exits that are present in a clause of the indicated length. -/ +def portsMatch (n : Fin 4) (a b c : Fin 4) (s : PortAssignment) : Bool := + (if 0 < n.val then s.1 == a else true) && + (if 1 < n.val then s.2.1 == b else true) && + (if 2 < n.val then s.2.2 == c else true) + +/-- Select the first ordered collar assignment extending the present source exits. -/ +def choosePorts (n : Fin 4) (a b c : Fin 4) : PortAssignment := + (collarAssignments.find? (portsMatch n a b c)).getD (0, 1, 2) + +/-- A partial source assignment needs pairwise distinct exits exactly at its present positions. -/ +def PresentDirectionsDistinct (n : Fin 4) (a b c : Fin 4) : Prop := + (2 ≤ n.val → a ≠ b) ∧ (3 ≤ n.val → a ≠ c ∧ b ≠ c) + +instance (n : Fin 4) (a b c : Fin 4) : Decidable (PresentDirectionsDistinct n a b c) := + inferInstanceAs (Decidable (_ ∧ _)) + +/-- Every admissible partial exit assignment has a deterministic distinct collar completion. -/ +theorem choosePorts_spec (n : Fin 4) (a b c : Fin 4) + (h : PresentDirectionsDistinct n a b c) : + (choosePorts n a b c).Distinct ∧ portsMatch n a b c (choosePorts n a b c) = true := by + have hall : ∀ (n : Fin 4) (a b c : Fin 4), PresentDirectionsDistinct n a b c → + (choosePorts n a b c).Distinct ∧ portsMatch n a b c (choosePorts n a b c) = true := by + decide +kernel + exact hall n a b c h + +/-- Each prescribed exit is retained by the selected collar assignment. -/ +theorem choosePorts_matches (n : Fin 4) (a b c : Fin 4) + (h : PresentDirectionsDistinct n a b c) : + (0 < n.val → (choosePorts n a b c).1 = a) ∧ + (1 < n.val → (choosePorts n a b c).2.1 = b) ∧ + (2 < n.val → (choosePorts n a b c).2.2 = c) := by + have hm := (choosePorts_spec n a b c h).2 + simp only [portsMatch, Bool.and_eq_true] at hm + constructor + · intro hn + simpa only [hn, ↓reduceIte, beq_iff_eq] using hm.1.1 + constructor + · intro hn + simpa only [hn, ↓reduceIte, beq_iff_eq] using hm.1.2 + · intro hn + simpa only [hn, ↓reduceIte, beq_iff_eq] using hm.2 + +/-- A literal position identifies its own incidence, including repeated literals. -/ +def sourceOccurrence (φ : CNF ℕ) (i j : ℕ) : Occurrence ℕ := + (i, j, (φ.getD i []).getD j (0, true)) + +/-- The actual source edge position of a clause/literal occurrence. -/ +def sourceEdgeIndex (φ : CNF ℕ) (i j : ℕ) : ℕ := + (Cslib.Logic.PL.occurrences φ).idxOf (sourceOccurrence φ i j) + +/-- The actual cardinal exit of one source literal. -/ +def literalSide (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) (i j : ℕ) : Fin 4 := + directionSide (sourceDirection (SAT.incidenceGraph φ) d (sourceEdgeIndex φ i j)) + +/-- Complete the present exits to a deterministic ordered collar. -/ +def clausePorts (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) (i : ℕ) : PortAssignment := + choosePorts ⟨min (φ.getD i []).length 3, by omega⟩ + (literalSide φ d i 0) (literalSide φ d i 1) (literalSide φ d i 2) + +/-- Existing clause and literal indices identify an actual source occurrence. -/ +theorem sourceOccurrence_mem (φ : CNF ℕ) {i j : ℕ} (hi : i < φ.length) + (hj : j < φ[i].length) : sourceOccurrence φ i j ∈ Cslib.Logic.PL.occurrences φ := by + rw [sourceOccurrence, List.getD_eq_getElem _ _ hi, List.getD_eq_getElem _ _ hj] + exact Cslib.Logic.PL.mem_occurrences.mpr ⟨hi, hj, rfl⟩ + +/-- Every actual source occurrence has a valid incidence-list index. -/ +theorem sourceEdgeIndex_lt (φ : CNF ℕ) {i j : ℕ} (hi : i < φ.length) + (hj : j < φ[i].length) : sourceEdgeIndex φ i j < (SAT.incidenceGraph φ).2.length := by + have h := List.idxOf_lt_length_of_mem (sourceOccurrence_mem φ hi hj) + simpa only [sourceEdgeIndex, SAT.incidenceGraph, List.length_map] using h + +/-- Source edge lookup recovers the actual variable and clause endpoints. -/ +theorem sourceEdgeIndex_edge (φ : CNF ℕ) {i j : ℕ} (hi : i < φ.length) + (hj : j < φ[i].length) : + (SAT.incidenceGraph φ).2.getD (sourceEdgeIndex φ i j) (0, 0) = + (φ.distinctVars.idxOf φ[i][j].1, φ.distinctVars.length + i) := by + have hm := sourceOccurrence_mem φ hi hj + have hindex := List.idxOf_lt_length_of_mem hm + unfold sourceEdgeIndex + rw [SAT.incidenceGraph, List.getD_eq_getElem _ _ (by simpa using hindex), List.getElem_map] + rw [List.getElem_idxOf hindex] + simp only [sourceOccurrence, + List.getD_eq_getElem _ _ hi, List.getD_eq_getElem _ _ hj] + +/-- Distinct source literal positions have distinct listed source edges. -/ +theorem sourceEdgeIndex_ne (φ : CNF ℕ) {i j k : ℕ} (hi : i < φ.length) + (hj : j < φ[i].length) (_hk : k < φ[i].length) (hjk : j ≠ k) : + sourceEdgeIndex φ i j ≠ sourceEdgeIndex φ i k := by + intro he + have he' := (List.idxOf_inj (sourceOccurrence_mem φ hi hj)).mp he + exact hjk (congrArg (fun o : Occurrence ℕ => o.2.1) he') + +/-- Actual source literal exits at the same clause have distinct collar sides. -/ +theorem literalSide_ne {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i j k : ℕ} + (hi : i < φ.length) (hj : j < φ[i].length) (hk : k < φ[i].length) (hjk : j ≠ k) : + literalSide φ d i j ≠ literalSide φ d i k := by + intro he + have hdij := sourceDirection_ne hd (sourceEdgeIndex_lt φ hi hj) (sourceEdgeIndex_lt φ hi hk) + (sourceEdgeIndex_ne φ hi hj hk hjk) (by + have h₁ := congrArg Prod.snd (sourceEdgeIndex_edge φ hi hj) + have h₂ := congrArg Prod.snd (sourceEdgeIndex_edge φ hi hk) + rw [List.getD_eq_getElem _ _ (sourceEdgeIndex_lt φ hi hj)] at h₁ + rw [List.getD_eq_getElem _ _ (sourceEdgeIndex_lt φ hi hk)] at h₂ + exact h₁.trans h₂.symm) + exact hdij (directionSide_injective (sourceDirection_unit _ _ _) (sourceDirection_unit _ _ _) he) + +/-- A valid source drawing supplies a distinct ordered collar for every actual clause. -/ +theorem clausePorts_spec {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) : + (clausePorts φ d i).Distinct ∧ + portsMatch ⟨min (φ.getD i []).length 3, by omega⟩ + (literalSide φ d i 0) (literalSide φ d i 1) (literalSide φ d i 2) + (clausePorts φ d i) = true := by + apply choosePorts_spec + constructor + · intro hn + change 2 ≤ min (φ.getD i []).length 3 at hn + rw [List.getD_eq_getElem _ _ hi] at hn + exact literalSide_ne hd hi (by omega) (by omega) (by decide) + · intro hn + change 3 ≤ min (φ.getD i []).length 3 at hn + rw [List.getD_eq_getElem _ _ hi] at hn + exact ⟨literalSide_ne hd hi (by omega) (by omega) (by decide), + literalSide_ne hd hi (by omega) (by omega) (by decide)⟩ + +/-- Each present source literal uses its actual cardinal side in the selected collar. -/ +theorem clausePorts_side {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) (j : Fin 3) (hj : j.val < φ[i].length) : + (clausePorts φ d i).side j = literalSide φ d i j.val := by + have hm := (clausePorts_spec hd hi).2 + simp only [portsMatch, Bool.and_eq_true] at hm + fin_cases j + · change 0 < φ[i].length at hj + have hn : 0 < min (φ.getD i []).length 3 := by + rw [List.getD_eq_getElem _ _ hi] + omega + have he := hm.1.1 + change (if 0 < min (φ.getD i []).length 3 then (clausePorts φ d i).1 == literalSide φ d i 0 + else true) = true at he + simp only [hn, ↓reduceIte, beq_iff_eq] at he + simpa [PortAssignment.side] using he + · change 1 < φ[i].length at hj + have hn : 1 < min (φ.getD i []).length 3 := by + rw [List.getD_eq_getElem _ _ hi] + omega + have he := hm.1.2 + change (if 1 < min (φ.getD i []).length 3 then (clausePorts φ d i).2.1 == literalSide φ d i 1 + else true) = true at he + simp only [hn, ↓reduceIte, beq_iff_eq] at he + simpa [PortAssignment.side] using he + · change 2 < φ[i].length at hj + have hn : 2 < min (φ.getD i []).length 3 := by + rw [List.getD_eq_getElem _ _ hi] + omega + have he := hm.2 + change (if 2 < min (φ.getD i []).length 3 then (clausePorts φ d i).2.2 == literalSide φ d i 2 + else true) = true at he + simp only [hn, ↓reduceIte, beq_iff_eq] at he + simpa [PortAssignment.side] using he + +/-- A collar's midpoint is its center plus its cardinal radius vector. -/ +theorem collarOuterPort_eq_center_add : ∀ s : Fin 4, + collarOuterPort s = (270000, 270000) + (270000 : ℕ) • sideDirection s := by + decide +kernel + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPositionLookup.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPositionLookup.lean new file mode 100644 index 000000000..82fe18e37 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPositionLookup.lean @@ -0,0 +1,66 @@ +/- +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.ExactOne.DrawingPositions +import Mathlib.Data.List.GetD + +/-! +# Local-to-global actual variable position lookup + +## Main results + +* `positiveVariablePoint_profile_auxiliary`: a private local profile variable resolves to its + prescribed actual clause-box point. + +## References + +This is an elementary consequence of the actual finite position-table construction. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- A local private variable resolves to its own point in the original clause's actual box. -/ +theorem positiveVariablePoint_profile_auxiliary (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + {c : Clause ℕ} {i : ℕ} (hci : (c, i) ∈ φ.zipIdx) + {w : CNF.PositiveExactOneCompile.Variable ℕ} {j : ℕ} + (hwj : (w, j) ∈ (profileVariables (clauseProfile c)).zipIdx) + (ha : ∀ n, w ≠ Sum.inl (Sum.inl n)) : + positiveVariablePoint φ d + (profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c w) = + positiveBoxOrigin φ d i + (clauseGeometry φ d i c).variablePosition j := by + apply positiveVariablePoint_auxiliary φ d + apply List.mem_flatMap.mpr + refine ⟨(c, i), hci, ?_⟩ + apply List.mem_filterMap.mpr + refine ⟨(w, j), hwj, ?_⟩ + rcases w with (n | ⟨a, b⟩) | ⟨⟨a, b⟩, s⟩ + · exact False.elim (ha n rfl) + · rfl + · rfl + +/-- Indexed lookup of an actual local private variable has its prescribed actual point. -/ +theorem positiveVariablePoint_profile_auxiliary_getD (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + {c : Clause ℕ} {i : ℕ} (hci : (c, i) ∈ φ.zipIdx) {j : ℕ} + (hj : j < (profileVariables (clauseProfile c)).length) + (ha : ∀ n, (profileVariables (clauseProfile c)).getD j (.inl (.inl 0)) ≠ + Sum.inl (Sum.inl n)) : + positiveVariablePoint φ d + (profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c + ((profileVariables (clauseProfile c)).getD j (.inl (.inl 0)))) = + positiveBoxOrigin φ d i + (clauseGeometry φ d i c).variablePosition j := by + apply positiveVariablePoint_profile_auxiliary φ d hci _ ha + apply List.mem_zipIdx_iff_getElem?.mpr + rw [List.getD_eq_getElem _ _ hj] + exact List.getElem?_eq_some_iff.mpr ⟨hj, rfl⟩ + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPositions.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPositions.lean new file mode 100644 index 000000000..190727044 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingPositions.lean @@ -0,0 +1,240 @@ +/- +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.ExactOne.Drawing +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingProfileBounds +import Mathlib.Data.List.Nodup +import Mathlib.Data.List.GetD + +/-! +# Uniquely indexed actual variable positions + +The finite position table contains each shared source variable once and each private auxiliary +in exactly one clause box. The private signed-clause intervals use the actual compiler's prefix +offsets. Thus table lookup returns the intended position without any auxiliary-name collision. + +## Main results + +* `positivePositionTable_keys_nodup`: every actual variable has a unique table key. +* `positiveVariablePoint_auxiliary`: actual auxiliary positions resolve to their local boxes. + +## References + +These support and index arguments are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- Whether a structured variable is private to a clause box. -/ +def isAuxiliaryVariable : CNF.PositiveExactOneCompile.Variable ℕ → Bool + | .inl (.inl _) => false + | _ => true + +/-- The auxiliary role excludes all inherited source-variable names. -/ +theorem isAuxiliaryVariable_iff (v : CNF.PositiveExactOneCompile.Variable ℕ) : + isAuxiliaryVariable v = true ↔ ∀ n, v ≠ Sum.inl (Sum.inl n) := by + rcases v with (v | v) | v + · simp [isAuxiliaryVariable] + · simp [isAuxiliaryVariable] + · simp [isAuxiliaryVariable] + +private theorem auxiliary_block_keys (vs : List (CNF.PositiveExactOneCompile.Variable ℕ)) + (f : CNF.PositiveExactOneCompile.Variable ℕ → CNF.PositiveExactOneCompile.Variable ℕ) + (point : ℕ → Point) (start : ℕ) : + ((vs.zipIdx start).filterMap fun v => match v.1 with + | .inl (.inl _) => none + | _ => some (f v.1, point v.2)).map Prod.fst = + (vs.filter isAuxiliaryVariable).map f := by + induction vs generalizing start with + | nil => rfl + | cons v vs ih => + rcases v with (v | ⟨a, b⟩) | ⟨⟨a, b⟩, s⟩ <;> + simp [List.zipIdx_cons, isAuxiliaryVariable, ih] <;> rfl + +/-- The actual fresh-position table lists exactly the mapped private profile variables. -/ +theorem positiveAuxiliaryBlock_keys (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) : + (positiveAuxiliaryBlock φ d i c).map Prod.fst = + ((profileVariables (clauseProfile c)).filter isAuxiliaryVariable).map + (profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c) := by + unfold positiveAuxiliaryBlock + exact auxiliary_block_keys (profileVariables (clauseProfile c)) + (profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c) + (fun j => positiveBoxOrigin φ d i + (clauseGeometry φ d i c).variablePosition j) 0 + +/-- A single actual clause block never repeats a fresh-variable key. -/ +theorem positiveAuxiliaryBlock_keys_nodup (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + {i : ℕ} (hi : i < φ.length) : + ((positiveAuxiliaryBlock φ d i φ[i]).map Prod.fst).Nodup := by + rw [positiveAuxiliaryBlock_keys] + apply List.Nodup.map_on + · intro v hv w hw he + have hv' := List.mem_filter.mp hv + have hw' := List.mem_filter.mp hw + exact (profileVariableMap_aux_injective φ hi hi hv'.1 hw'.1 + ((isAuxiliaryVariable_iff v).mp hv'.2) ((isAuxiliaryVariable_iff w).mp hw'.2) he).2 + · exact (CNF.nodup_distinctVars (profileFormula (clauseProfile φ[i]))).filter _ + +/-- Different original clause boxes have disjoint fresh-variable key lists. -/ +theorem positiveAuxiliaryBlock_keys_disjoint (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + {i j : ℕ} (hi : i < φ.length) (hj : j < φ.length) (hij : i ≠ j) : + List.Disjoint ((positiveAuxiliaryBlock φ d i φ[i]).map Prod.fst) + ((positiveAuxiliaryBlock φ d j φ[j]).map Prod.fst) := by + rw [List.disjoint_left, positiveAuxiliaryBlock_keys, positiveAuxiliaryBlock_keys] + intro key hki hkj + obtain ⟨v, hv, hve⟩ := List.mem_map.mp hki + obtain ⟨w, hw, hwe⟩ := List.mem_map.mp hkj + have hv' := List.mem_filter.mp hv + have hw' := List.mem_filter.mp hw + exact hij (profileVariableMap_aux_injective φ hi hj hv'.1 hw'.1 + ((isAuxiliaryVariable_iff v).mp hv'.2) ((isAuxiliaryVariable_iff w).mp hw'.2) + (hve.trans hwe.symm)).1 + +/-- The global fresh-variable position table has no repeated key. -/ +theorem positiveAuxiliaryPositions_keys_nodup (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + ((positiveAuxiliaryPositions φ d).map Prod.fst).Nodup := by + rw [positiveAuxiliaryPositions, List.map_flatMap] + apply List.nodup_flatMap.mpr + constructor + · intro ⟨c, i⟩ hc + obtain ⟨hi, he⟩ := List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hc) + change i < φ.length at hi + change φ[i] = c at he + change ((positiveAuxiliaryBlock φ d i c).map Prod.fst).Nodup + rw [← he] + exact positiveAuxiliaryBlock_keys_nodup φ d hi + · apply List.pairwise_iff_getElem.mpr + intro i j hi hj hij + have hi' : i < φ.length := by simpa only [List.length_zipIdx] using hi + have hj' : j < φ.length := by simpa only [List.length_zipIdx] using hj + simpa only [Function.onFun, List.getElem_zipIdx, Nat.zero_add] using + positiveAuxiliaryBlock_keys_disjoint φ d hi' hj' (Nat.ne_of_lt hij) + +/-- No fresh position-table key can be an inherited source-variable name. -/ +theorem positiveAuxiliaryPositions_ne_source (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + {v : CNF.PositiveExactOneCompile.Variable ℕ} + (hv : v ∈ (positiveAuxiliaryPositions φ d).map Prod.fst) (n : ℕ) : + v ≠ Sum.inl (Sum.inl n) := by + rw [positiveAuxiliaryPositions, List.map_flatMap] at hv + obtain ⟨c, _, hv⟩ := List.mem_flatMap.mp hv + rw [positiveAuxiliaryBlock_keys] at hv + obtain ⟨w, hw, rfl⟩ := List.mem_map.mp hv + exact profileVariableMap_aux_ne_source + ((isAuxiliaryVariable_iff w).mp (List.mem_filter.mp hw).2) n + +/-- Every actual variable position has a unique finite-table key. -/ +theorem positivePositionTable_keys_nodup (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + ((positivePositionTable φ d).map Prod.fst).Nodup := by + rw [positivePositionTable, List.map_append, List.nodup_append] + refine ⟨?_, positiveAuxiliaryPositions_keys_nodup φ d, ?_⟩ + · simp only [positiveSourcePositions, List.map_map, Function.comp_def] + exact (CNF.nodup_distinctVars φ).map (fun _ _ h => Sum.inl.inj (Sum.inl.inj h)) + · intro v hv w hw he + simp only [positiveSourcePositions, List.map_map, Function.comp_def, List.mem_map] at hv + obtain ⟨n, _, rfl⟩ := hv + exact positiveAuxiliaryPositions_ne_source φ d hw n he.symm + +/-- An actual auxiliary entry resolves to its own displayed clause-box point. -/ +theorem positiveVariablePoint_auxiliary (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + {v : CNF.PositiveExactOneCompile.Variable ℕ} {p : Point} + (hm : (v, p) ∈ positiveAuxiliaryPositions φ d) : positiveVariablePoint φ d v = p := + positiveVariablePoint_of_mem φ d (positivePositionTable_keys_nodup φ d) + (List.mem_append.mpr (Or.inr hm)) + +/-- Every actual output variable belongs to a source-clause profile with the exact global name. -/ +theorem rawVariable_profile (φ : CNF ℕ) {v : CNF.PositiveExactOneCompile.Variable ℕ} + (hv : v ∈ (CNF.PositiveExactOneCompile.rawFormula φ).vars) : + ∃ (c : Clause ℕ) (i : ℕ), (c, i) ∈ φ.zipIdx ∧ + ∃ w ∈ profileVariables (clauseProfile c), + profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c w = v := by + rw [rawFormula_eq_profiles] at hv + obtain ⟨cl, hcl, b, hl⟩ := CNF.mem_vars.mp hv + obtain ⟨⟨c, i⟩, hci, hcl⟩ := List.mem_flatMap.mp hcl + obtain ⟨localClause, hc, rfl⟩ := List.mem_map.mp hcl + obtain ⟨lit, hlit, he⟩ := List.mem_map.mp hl + refine ⟨c, i, hci, lit.1, CNF.mem_distinctVars.mpr (CNF.mem_vars.mpr + ⟨localClause, hc, lit.2, hlit⟩), ?_⟩ + exact congrArg Prod.fst he + +/-- Every private profile variable contributes its prescribed fresh-position entry. -/ +theorem positiveAuxiliaryBlock_entry (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) {w : CNF.PositiveExactOneCompile.Variable ℕ} + (hw : w ∈ profileVariables (clauseProfile c)) (ha : ∀ n, w ≠ Sum.inl (Sum.inl n)) : + ∃ p, (profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c w, p) ∈ + positiveAuxiliaryBlock φ d i c := by + obtain ⟨j, hj, he⟩ := List.mem_iff_getElem.mp hw + refine ⟨positiveBoxOrigin φ d i + (clauseGeometry φ d i c).variablePosition j, ?_⟩ + apply List.mem_filterMap.mpr + refine ⟨(w, j), ?_, ?_⟩ + · exact List.mem_zipIdx_iff_getElem?.mpr (List.getElem?_eq_some_iff.mpr ⟨hj, he⟩) + · rcases w with (n | ⟨a, b⟩) | ⟨⟨a, b⟩, s⟩ + · exact False.elim (ha n rfl) + · rfl + · rfl + +/-- Every occurring actual output variable has a finite position-table entry. -/ +theorem positivePositionTable_covers (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + {v : CNF.PositiveExactOneCompile.Variable ℕ} + (hv : v ∈ (CNF.PositiveExactOneCompile.rawFormula φ).vars) : + ∃ p, (v, p) ∈ positivePositionTable φ d := by + obtain ⟨c, i, hci, w, hw, rfl⟩ := rawVariable_profile φ hv + have hscope := profileVariables_scope (clauseProfile c) w hw + rcases w with (j | ⟨a, b⟩) | ⟨⟨a, b⟩, s⟩ + · have hj : j < c.length := hscope.trans_le (profileSource_length_le c) + have hvar : (c.getD j (0, true)).1 ∈ φ.vars := by + apply CNF.mem_vars.mpr + refine ⟨c, List.fst_mem_of_mem_zipIdx hci, (c.getD j (0, true)).2, ?_⟩ + rw [List.getD_eq_getElem _ _ hj] + exact List.getElem_mem hj + refine ⟨positiveScale • Graph.drawingPosition (SAT.incidenceGraph φ) d + (φ.distinctVars.idxOf (c.getD j (0, true)).1) + positiveTranslation, ?_⟩ + apply List.mem_append.mpr + apply Or.inl + exact List.mem_map.mpr ⟨(c.getD j (0, true)).1, CNF.mem_distinctVars.mpr hvar, rfl⟩ + · obtain ⟨p, hp⟩ := positiveAuxiliaryBlock_entry φ d i c hw (by simp) + exact ⟨p, List.mem_append.mpr (Or.inr (List.mem_flatMap.mpr ⟨(c, i), hci, hp⟩))⟩ + · obtain ⟨p, hp⟩ := positiveAuxiliaryBlock_entry φ d i c hw (by simp) + exact ⟨p, List.mem_append.mpr (Or.inr (List.mem_flatMap.mpr ⟨(c, i), hci, hp⟩))⟩ + +/-- Every fresh-position record has an actual original-clause owner and a unique local profile +index. Private source placeholders are excluded from these records. -/ +theorem positiveAuxiliaryPositions_origin (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + {v : CNF.PositiveExactOneCompile.Variable ℕ} {p : Point} + (hp : (v, p) ∈ positiveAuxiliaryPositions φ d) : + ∃ (c : Clause ℕ) (i : ℕ), (c, i) ∈ φ.zipIdx ∧ + ∃ w ∈ profileVariables (clauseProfile c), (∀ n, w ≠ Sum.inl (Sum.inl n)) ∧ + v = profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c w ∧ + p = positiveBoxOrigin φ d i + (clauseGeometry φ d i c).variablePosition + ((profileVariables (clauseProfile c)).idxOf w) := by + obtain ⟨⟨c, i⟩, hci, hp⟩ := List.mem_flatMap.mp hp + obtain ⟨⟨w, j⟩, hwj, he⟩ := List.mem_filterMap.mp hp + have hj := List.mem_zipIdx_iff_getElem?.mp hwj + obtain ⟨hjlt, hget⟩ := List.getElem?_eq_some_iff.mp hj + have hwm : w ∈ profileVariables (clauseProfile c) := List.fst_mem_of_mem_zipIdx hwj + have hidx : (profileVariables (clauseProfile c)).idxOf w = j := by + have hn := CNF.nodup_distinctVars (profileFormula (clauseProfile c)) + have h := hn.idxOf_getElem j hjlt + change (profileVariables (clauseProfile c))[j] = w at hget + change (profileVariables (clauseProfile c)).idxOf + ((profileVariables (clauseProfile c))[j]) = j at h + rwa [hget] at h + rcases w with (n | ⟨a, b⟩) | ⟨⟨a, b⟩, s⟩ + · simp at he + · simp only [Option.some.injEq, Prod.mk.injEq] at he + refine ⟨c, i, hci, _, hwm, by simp, he.1.symm, ?_⟩ + simpa only [hidx] using he.2.symm + · simp only [Option.some.injEq, Prod.mk.injEq] at he + refine ⟨c, i, hci, _, hwm, by simp, he.1.symm, ?_⟩ + simpa only [hidx] using he.2.symm + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingProfileBounds.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingProfileBounds.lean new file mode 100644 index 000000000..2bfd483e1 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingProfileBounds.lean @@ -0,0 +1,91 @@ +/- +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.ExactOne.DrawingProfiles +public import Complexity.Logics.Propositional.CNF.ExactOne.BlockBounds + +/-! +# Unique ownership of local drawing auxiliaries + +The original-clause index distinguishes signed auxiliaries; disjoint signed-clause prefix +intervals distinguish positive auxiliaries. Source placeholders may coincide when source +variables are repeated, and are deliberately excluded from these injectivity statements. + +## Main results + +* `profileVariableMap_aux_injective`: an actual auxiliary identifies its unique source block + and local profile variable. +* `profileVariableMap_aux_ne_source`: private auxiliary names cannot be source names. + +## References + +These are original support and prefix-index arguments for the actual drawing compiler. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL + +/-- Signed block length depends on the clause length, independently of source variable names +and polarities. The selected profile has exactly the actual signed block length. -/ +theorem profile_signed_length (i : ℕ) (c : Clause ℕ) : + (CNF.FromSAT.clause 0 (profileSource (clauseProfile c))).length = + (CNF.FromSAT.clause i c).length := by + rcases c with _ | ⟨⟨p, bp⟩, _ | ⟨⟨q, bq⟩, _ | ⟨⟨r, br⟩, rest⟩⟩⟩ + · rfl + · cases bp <;> rfl + · cases bp <;> cases bq <;> rfl + · cases bp <;> cases bq <;> cases br <;> rfl + +/-- A private auxiliary never becomes an inherited source-variable name. -/ +theorem profileVariableMap_aux_ne_source {i offset : ℕ} {c : Clause ℕ} + {v : CNF.PositiveExactOneCompile.Variable ℕ} + (hv : ∀ n, v ≠ Sum.inl (Sum.inl n)) (n : ℕ) : + profileVariableMap i offset c v ≠ Sum.inl (Sum.inl n) := by + rcases v with (_ | _) | _ + · exact False.elim (hv _ rfl) + · simp [profileVariableMap] + · simp [profileVariableMap] + +/-- The actual global name of an auxiliary determines its source block and its local profile +name. Source placeholders, whose variables can coincide, are excluded explicitly. -/ +theorem profileVariableMap_aux_injective (φ : CNF ℕ) {i j : ℕ} + (hi : i < φ.length) (hj : j < φ.length) + {v w : CNF.PositiveExactOneCompile.Variable ℕ} + (hv : v ∈ profileVariables (clauseProfile φ[i])) + (hw : w ∈ profileVariables (clauseProfile φ[j])) + (hvs : ∀ n, v ≠ Sum.inl (Sum.inl n)) + (hws : ∀ n, w ≠ Sum.inl (Sum.inl n)) + (he : profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) φ[i] v = + profileVariableMap j (CNF.PositiveExactOneCompile.signedOffset φ j) φ[j] w) : + i = j ∧ v = w := by + have hscopeV := profileVariables_scope (clauseProfile φ[i]) v hv + have hscopeW := profileVariables_scope (clauseProfile φ[j]) w hw + rcases v with (a | ⟨a, b⟩) | ⟨⟨a, b⟩, s⟩ + · exact False.elim (hvs a rfl) + · rcases w with (c | ⟨c, d⟩) | ⟨⟨c, d⟩, t⟩ + · exact False.elim (hws c rfl) + · simp only [profileVariableMap, Sum.inl.injEq, Sum.inr.injEq, Prod.mk.injEq] at he + exact ⟨he.1, by simp only [hscopeV, hscopeW, he.2]⟩ + · simp [profileVariableMap] at he + · rcases w with (c | ⟨c, d⟩) | ⟨⟨c, d⟩, t⟩ + · exact False.elim (hws c rfl) + · simp [profileVariableMap] at he + · simp only [profileVariableMap, Sum.inr.injEq, Prod.mk.injEq] at he + have ha : a < (CNF.FromSAT.clause i φ[i]).length := by + simpa only [profile_signed_length i φ[i]] using hscopeV + have hc : c < (CNF.FromSAT.clause j φ[j]).length := by + simpa only [profile_signed_length j φ[j]] using hscopeW + obtain ⟨hij, hac⟩ := CNF.PositiveExactOneCompile.signedOffset_range_injective + φ hi hj ha hc he.1.1 + exact ⟨hij, by simp only [hac, he.1.2, he.2]⟩ + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingProfiles.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingProfiles.lean new file mode 100644 index 000000000..69c1a6137 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingProfiles.lean @@ -0,0 +1,202 @@ +/- +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.ExactOne.GeometryProfilesData +public import Complexity.Logics.Propositional.CNF.ExactOne.Blocks +import Mathlib.Tactic.FinCases + +/-! +# Actual variable names of local positive exact-one profiles + +The finite geometric profiles use source literal positions as placeholder variable names. +Relabelling restores source labels, original clause indices, and the exact signed-clause prefix +offsets of the positive compiler. Repeated source variables may identify distinct placeholders; +their displayed vertices are omitted when the local incidences are spliced to source routes. + +## Main definitions + +* `clauseProfile`: select the finite profile from source length and polarities. +* `profileVariableMap`: restore actual labels and both families of auxiliary indices. + +## Main results + +* `profileFormula_relabel`: the relabelled profile is the actual positive output block. +* `rawFormula_eq_profiles`: the profile list has exactly the actual compiler's clause order. + +## References + +These are original indexing identities for the existing parsimonious compiler. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL + +/-- Select one of the fifteen profiles from the first three source literal polarities. -/ +def clauseProfile : Clause ℕ → ClauseProfile + | [] => 0 + | [p] => ⟨1 + p.2.toNat, by have := Bool.toNat_le p.2; omega⟩ + | [p, q] => ⟨3 + 2 * p.2.toNat + q.2.toNat, by + have := Bool.toNat_le p.2 + have := Bool.toNat_le q.2 + omega⟩ + | p :: q :: r :: _ => ⟨7 + 4 * p.2.toNat + 2 * q.2.toNat + r.2.toNat, by + have := Bool.toNat_le p.2 + have := Bool.toNat_le q.2 + have := Bool.toNat_le r.2 + omega⟩ + +/-- Restore actual source labels, the original clause index, and the global signed-clause offset. +The source-slot fallback is irrelevant to every occurring profile variable. -/ +def profileVariableMap (i offset : ℕ) (c : Clause ℕ) : + CNF.PositiveExactOneCompile.Variable ℕ → CNF.PositiveExactOneCompile.Variable ℕ + | .inl (.inl j) => .inl (.inl (c.getD j (0, true)).1) + | .inl (.inr (_, k)) => .inl (.inr (i, k)) + | .inr ((j, k), s) => .inr ((offset + j, k), s) + +/-- A finite profile has exactly the actual compiler's literals after restoring all indices. -/ +theorem profileFormula_relabel (i offset : ℕ) (c : Clause ℕ) : + (profileFormula (clauseProfile c)).relabel (profileVariableMap i offset c) = + (CNF.PositiveExactOne.formula (CNF.FromSAT.clause i c)).relabel + (CNF.PositiveExactOne.shift offset) := by + rcases c with _ | ⟨⟨p, bp⟩, _ | ⟨⟨q, bq⟩, _ | ⟨⟨r, br⟩, rest⟩⟩⟩ + · simp [clauseProfile, profileFormula, profileSource, profileVariableMap, + CNF.FromSAT.clause, CNF.FromSAT.core, CNF.FromSAT.padding, CNF.FromSAT.aux, + CNF.FromSAT.forcePadding, CNF.PositiveExactOne.formula, + CNF.PositiveExactOne.clauseClauses, CNF.PositiveExactOne.positiveLiteral, + CNF.PositiveExactOne.complementBlock, CNF.PositiveExactOne.auxiliary, + CNF.PositiveExactOne.shift, CNF.relabel, Clause.relabel, + CNF.ExactOneGadget.disjunctionClauses, CNF.ExactOneGadget.forceFalseClauses, + CNF.ExactOneGadget.complementClauses, Std.Sat.Literal.negate] + · cases bp <;> + simp [clauseProfile, profileFormula, profileSource, profileVariableMap, + CNF.FromSAT.clause, CNF.FromSAT.core, CNF.FromSAT.padding, CNF.FromSAT.aux, + CNF.FromSAT.lift, CNF.FromSAT.forcePadding, CNF.PositiveExactOne.formula, + CNF.PositiveExactOne.clauseClauses, CNF.PositiveExactOne.positiveLiteral, + CNF.PositiveExactOne.complementBlock, CNF.PositiveExactOne.auxiliary, + CNF.PositiveExactOne.shift, CNF.relabel, Clause.relabel, + CNF.ExactOneGadget.disjunctionClauses, CNF.ExactOneGadget.forceFalseClauses, + CNF.ExactOneGadget.complementClauses, Std.Sat.Literal.negate, List.getD] + · cases bp <;> cases bq <;> + simp [clauseProfile, profileFormula, profileSource, profileVariableMap, + CNF.FromSAT.clause, CNF.FromSAT.core, CNF.FromSAT.padding, CNF.FromSAT.aux, + CNF.FromSAT.lift, CNF.FromSAT.forcePadding, CNF.PositiveExactOne.formula, + CNF.PositiveExactOne.clauseClauses, CNF.PositiveExactOne.positiveLiteral, + CNF.PositiveExactOne.complementBlock, CNF.PositiveExactOne.auxiliary, + CNF.PositiveExactOne.shift, CNF.relabel, Clause.relabel, + CNF.ExactOneGadget.disjunctionClauses, CNF.ExactOneGadget.forceFalseClauses, + CNF.ExactOneGadget.complementClauses, Std.Sat.Literal.negate, List.getD] + · cases bp <;> cases bq <;> cases br <;> + simp [clauseProfile, profileFormula, profileSource, profileVariableMap, + CNF.FromSAT.clause, CNF.FromSAT.core, CNF.FromSAT.aux, + CNF.FromSAT.lift, CNF.PositiveExactOne.formula, + CNF.PositiveExactOne.clauseClauses, CNF.PositiveExactOne.positiveLiteral, + CNF.PositiveExactOne.complementBlock, CNF.PositiveExactOne.auxiliary, + CNF.PositiveExactOne.shift, CNF.relabel, Clause.relabel, + CNF.ExactOneGadget.disjunctionClauses, CNF.ExactOneGadget.forceFalseClauses, + CNF.ExactOneGadget.complementClauses, Std.Sat.Literal.negate, List.getD] + +/-- The profile partition retains the actual output formula, clause order, and variable names. -/ +theorem rawFormula_eq_profiles (φ : CNF ℕ) : + CNF.PositiveExactOneCompile.rawFormula φ = φ.zipIdx.flatMap fun c => + (profileFormula (clauseProfile c.1)).relabel + (profileVariableMap c.2 (CNF.PositiveExactOneCompile.signedOffset φ c.2) c.1) := by + rw [CNF.PositiveExactOneCompile.rawFormula_eq_flatMap] + simp only [List.flatMap_def] + apply congrArg List.flatten + apply List.map_congr_left + intro c _ + exact (profileFormula_relabel _ _ _).symm + +/-- Every actual positive replacement profile has at most twenty-two clauses. -/ +theorem profileFormula_length_le (p : ClauseProfile) : (profileFormula p).length ≤ 22 := by + fin_cases p <;> decide +kernel + +/-- The local incidence table has one route per literal, in each actual profile clause. -/ +theorem profileGeometry_incidence_count (p : ClauseProfile) (i : Fin 22) : + ((profileGeometry p).incidenceRoutes.filter fun e => e.1 == i.val).length = + ((profileFormula p).getD i.val []).length := by + fin_cases p <;> fin_cases i <;> decide +kernel + +/-- Filtering the incidence table by clause retains the actual local literal order and names. -/ +theorem profileGeometry_literal_indices (p : ClauseProfile) (i : Fin 22) + {l : Literal (CNF.PositiveExactOneCompile.Variable ℕ)} {e : LocalIncidence} + (he : (l, e) ∈ ((profileFormula p).getD i.val []).zip + ((profileGeometry p).incidenceRoutes.filter fun e => e.1 == i.val)) : + e.2.1 = (profileVariables p).idxOf l.1 := by + have hall : (((profileFormula p).getD i.val []).zip + ((profileGeometry p).incidenceRoutes.filter fun e => e.1 == i.val)).all + (fun a => a.2.2.1 == (profileVariables p).idxOf a.1.1) = true := by + fin_cases p <;> fin_cases i <;> decide +kernel + exact beq_iff_eq.mp (List.all_eq_true.mp hall (l, e) he) + +/-- Every profile variable has exactly its indicated source, original-clause, or signed-clause +scope. These finite bounds identify the private auxiliary intervals used by global assembly. -/ +theorem profileVariables_scope (p : ClauseProfile) : + ∀ v ∈ profileVariables p, + match v with + | .inl (.inl j) => j < (profileSource p).length + | .inl (.inr (i, _)) => i = 0 + | .inr ((i, _), _) => i < (CNF.FromSAT.clause 0 (profileSource p)).length := by + have hall : (profileVariables p).all (fun v => + match v with + | .inl (.inl j) => decide (j < (profileSource p).length) + | .inl (.inr (i, _)) => decide (i = 0) + | .inr ((i, _), _) => + decide (i < (CNF.FromSAT.clause 0 (profileSource p)).length)) = true := by + fin_cases p <;> decide +kernel + intro v hv + have h := List.all_eq_true.mp hall v hv + cases v with + | inl v => + cases v with + | inl j => simpa only [decide_eq_true_eq] using h + | inr a => rcases a with ⟨i, k⟩; simpa only [decide_eq_true_eq] using h + | inr a => rcases a with ⟨⟨i, k⟩, s⟩; simpa only [decide_eq_true_eq] using h + +/-- Every present source placeholder has a valid local variable index and its prescribed +position in the ordered terminal list. -/ +theorem profileSource_terminal_index (p : ClauseProfile) (j : Fin 3) + (hj : j.val < (profileSource p).length) : + let v := (profileVariables p).idxOf (Sum.inl (Sum.inl j.val)) + v < (profileGeometry p).variablePositions.length ∧ + v ∈ (profileGeometry p).terminalVariables ∧ + (profileGeometry p).terminalVariables.idxOf v = j.val := by + have hall : ∀ (p : ClauseProfile) (j : Fin 3), j.val < (profileSource p).length → + let v := (profileVariables p).idxOf (Sum.inl (Sum.inl j.val)) + v < (profileGeometry p).variablePositions.length ∧ + v ∈ (profileGeometry p).terminalVariables ∧ + (profileGeometry p).terminalVariables.idxOf v = j.val := by + decide +kernel + exact hall p j hj + +/-- Selecting a profile preserves the source width when the source has width at most three. -/ +theorem profileSource_length (c : Clause ℕ) (hc : c.length ≤ 3) : + (profileSource (clauseProfile c)).length = c.length := by + rcases c with _ | ⟨⟨p, bp⟩, _ | ⟨⟨q, bq⟩, _ | ⟨⟨r, br⟩, rest⟩⟩⟩ + · rfl + · cases bp <;> rfl + · cases bp <;> cases bq <;> rfl + · have hr : rest = [] := by simpa using hc + subst rest + cases bp <;> cases bq <;> cases br <;> rfl + +/-- Profiles retain at most the first three original positions. -/ +theorem profileSource_length_le (c : Clause ℕ) : + (profileSource (clauseProfile c)).length ≤ c.length := by + rcases c with _ | ⟨⟨p, bp⟩, _ | ⟨⟨q, bq⟩, _ | ⟨⟨r, br⟩, rest⟩⟩⟩ + · rfl + · cases bp <;> rfl + · cases bp <;> cases bq <;> rfl + · cases bp <;> cases bq <;> cases br <;> + simp [clauseProfile, profileSource] + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingRouteContacts.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingRouteContacts.lean new file mode 100644 index 000000000..beb20e694 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingRouteContacts.lean @@ -0,0 +1,374 @@ +/- +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.ExactOne.DrawingRoutes +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingVertexBoxes +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingPositionLookup +import Mathlib.Data.List.GetD + +/-! +# Global continuous contacts of the actual positive incidence routes + +## Main results + +* `positiveStem_different_boxes_disjoint`: closed replacement boxes stay disjoint. +* `positiveStem_foreign_exterior_disjoint`: exterior routes avoid other clause boxes. +* `positiveIncidenceRoute_endpoints`: actual clause and globally shared variable endpoints. +* `positiveIncidenceRoute_contacts`: full real-support contacts only at actual common endpoints. + +## References + +These continuous route-composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- Local prefixes belonging to different original clauses have disjoint full supports. -/ +theorem positiveStem_different_boxes_disjoint {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i j : ℕ} + (hi : i < φ.length) (hj : j < φ.length) (hij : i ≠ j) + (c c' : Clause ℕ) {e f : LocalIncidence} + (he : e ∈ (clauseGeometry φ d i c).incidenceRoutes) + (hf : f ∈ (clauseGeometry φ d j c').incidenceRoutes) : + Disjoint (support (positiveStem φ d i e)) (support (positiveStem φ d j f)) := by + rw [Set.disjoint_left] + intro z hz hw + have hb := positiveStem_in_box hd hi c he hz + have hc := positiveStem_in_box hd hj c' hf hw + have hb' : inPointBox (positiveBoxOrigin φ d i) positiveRadius + (z - realPoint positiveTranslation) := by + apply (inPointBox_translate_iff _ positiveTranslation _ _).mp + simpa only [sub_add_cancel] using hb + have hc' : inPointBox (positiveBoxOrigin φ d j) positiveRadius + (z - realPoint positiveTranslation) := by + apply (inPointBox_translate_iff _ positiveTranslation _ _).mp + simpa only [sub_add_cancel] using hc + have heq := eq_of_inPointBoxes (by decide : 2 * positiveRadius < positiveScale) hb' hc' + have hlabels := drawingPosition_injOn hd (sourceClause_declared φ hi) + (sourceClause_declared φ hj) heq + exact hij (Nat.add_left_cancel hlabels) + +/-- An inherited exterior route avoids every other original clause's entire closed box. -/ +theorem positiveExterior_foreign_box {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i j k : ℕ} + (hi : i < φ.length) (hj : j < φ.length) (hk : k < φ[j].length) (hij : i ≠ j) + {z : ℝ × ℝ} (hz : z ∈ support (positiveExterior φ d j k)) : + ¬ inPointBox (positiveBoxOrigin φ d i + positiveTranslation) positiveRadius z := by + rw [positiveExterior, support_translate] at hz + obtain ⟨x, hx, rfl⟩ := hz + rw [inPointBox_translate_iff] + have hindex := sourceEdgeIndex_lt φ hj hk + have hedge : (SAT.incidenceGraph φ).2[sourceEdgeIndex φ j k] = + (φ.distinctVars.idxOf φ[j][k].1, φ.distinctVars.length + j) := by + rw [← List.getD_eq_getElem _ _ hindex] + exact sourceEdgeIndex_edge φ hj hk + apply exteriorRoute_avoids_box hd hindex (sourceClause_declared φ hi) ?_ ?_ + (by decide : positiveRadius < positiveScale) hx + · rw [hedge] + have hv : φ[j][k].1 ∈ φ.distinctVars := CNF.mem_distinctVars.mpr (CNF.mem_vars.mpr + ⟨φ[j], List.getElem_mem hj, φ[j][k].2, List.getElem_mem hk⟩) + have hlt := List.idxOf_lt_length_of_mem hv + omega + · rw [hedge] + omega + +/-- A finite local prefix and an inherited route from another clause are disjoint. -/ +theorem positiveStem_foreign_exterior_disjoint {φ : CNF ℕ} + {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i j k : ℕ} + (hi : i < φ.length) (hj : j < φ.length) (hk : k < φ[j].length) (hij : i ≠ j) + (c : Clause ℕ) {e : LocalIncidence} + (he : e ∈ (clauseGeometry φ d i c).incidenceRoutes) : + Disjoint (support (positiveStem φ d i e)) (support (positiveExterior φ d j k)) := by + rw [Set.disjoint_left] + intro z hz hw + exact positiveExterior_foreign_box hd hi hj hk hij hw (positiveStem_in_box hd hi c he hz) + +/-- The actual global variable attached to one local incidence row. -/ +def positiveIncidenceVariable (φ : CNF ℕ) (i : ℕ) (c : Clause ℕ) + (e : LocalIncidence) : CNF.PositiveExactOneCompile.Variable ℕ := + profileVariableMap i (CNF.PositiveExactOneCompile.signedOffset φ i) c + ((profileVariables (clauseProfile c)).getD e.2.1 (.inl (.inl 0))) + +/-- Actual emitted incidences have their specified local clause and globally shared variable +endpoints. Private variables resolve through the actual finite position table. -/ +theorem positiveIncidenceRoute_endpoints {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile φ[i]))) + {e : LocalIncidence} (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) : + (positiveIncidenceRoute φ d i φ[i] e).head? = + some (positiveBoxOrigin φ d i + (clauseGeometry φ d i φ[i]).clausePosition e.1) ∧ + (positiveIncidenceRoute φ d i φ[i] e).getLast? = + some (positiveVariablePoint φ d (positiveIncidenceVariable φ i φ[i] e)) := by + have hstem := positiveStem_regular hd hi φ[i] h he + have hends := collaredProfile_incidence_endpoints _ h (clausePorts_spec hd hi).1 he + have hv : e.2.1 < (profilePrefixGeometry (clauseProfile φ[i])).variablePositions.length := by + simpa only [collaredPrefixGeometry, List.length_map, List.length_zipIdx] using hends.2.1 + have hlen : (profilePrefixGeometry (clauseProfile φ[i])).variablePositions.length = + (profileVariables (clauseProfile φ[i])).length := by + simpa only [profilePrefixGeometry, LocalGeometry.toPrefix, List.length_map, + List.length_zipIdx] using profileGeometry_variable_length (clauseProfile φ[i]) + unfold positiveIncidenceRoute + split + next j hj => + have hm := profile_source_lookup _ hv hj + have hjSource : j < φ[i].length := lt_of_lt_of_le hm.1 (profileSource_length_le φ[i]) + obtain ⟨q, hq, hr⟩ := positiveIncidenceRoute_join_endpoints hd hi h he hj + change (positiveStem φ d i e ++ (positiveExterior φ d i j).tail).head? = _ ∧ + (positiveStem φ d i e ++ (positiveExterior φ d i j).tail).getLast? = _ + refine ⟨(by + obtain ⟨t, ht⟩ := List.head?_eq_some_iff.mp hstem.2.2.1 + rw [ht] + rfl), ?_⟩ + rw [last_join hq hr, positiveExterior_last hd hi hjSource] + unfold positiveIncidenceVariable + rw [hj] + simp only [profileVariableMap, List.getD_eq_getElem _ _ hjSource] + next w hw => + change (positiveStem φ d i e).head? = _ ∧ (positiveStem φ d i e).getLast? = _ + refine ⟨hstem.2.2.1, ?_⟩ + rw [hstem.2.2.2] + congr 1 + symm + apply positiveVariablePoint_profile_auxiliary_getD φ d + (List.mem_zipIdx_iff_getElem?.mpr (List.getElem?_eq_some_iff.mpr ⟨hi, rfl⟩)) + (by omega) + exact hw + +/-- Full support of an emitted route splits into its finite stem and, for a source +placeholder only, the inherited exterior support. -/ +theorem mem_support_positiveIncidenceRoute_iff {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile φ[i]))) + {e : LocalIncidence} (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) + {z : ℝ × ℝ} : + z ∈ support (positiveIncidenceRoute φ d i φ[i] e) ↔ + z ∈ support (positiveStem φ d i e) ∨ + ∃ j, (profileVariables (clauseProfile φ[i])).getD e.2.1 (.inl (.inl 0)) = + .inl (.inl j) ∧ z ∈ support (positiveExterior φ d i j) := by + unfold positiveIncidenceRoute + split + next j hj => + change z ∈ support (positiveStem φ d i e ++ (positiveExterior φ d i j).tail) ↔ _ + obtain ⟨q, hq, hr⟩ := positiveIncidenceRoute_join_endpoints hd hi h he hj + rw [support_join hq hr, Set.mem_union] + constructor + · exact fun hz => hz.elim Or.inl (fun hz => Or.inr ⟨j, hj, hz⟩) + · rintro (hz | ⟨k, hk, hz⟩) + · exact Or.inl hz + · have hkj : k = j := Sum.inl.inj (Sum.inl.inj (hk.symm.trans hj)) + exact Or.inr (hkj ▸ hz) + next w hw => + change z ∈ support (positiveStem φ d i e) ↔ _ + constructor + · exact Or.inl + · rintro (hz | ⟨k, hk, _⟩) + · exact hz + · exact False.elim (hw k hk) + +/-- Distinct inherited source incidences meet only at an actual common source-variable point, +including repeated literals and distinct occurrences with equal signed variable names. -/ +theorem positiveExterior_contacts {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i j k l : ℕ} + (hi : i < φ.length) (hj : j < φ.length) (hk : k < φ[i].length) (hl : l < φ[j].length) + (hne : (i, k) ≠ (j, l)) {z : ℝ × ℝ} + (hz : z ∈ support (positiveExterior φ d i k)) + (hw : z ∈ support (positiveExterior φ d j l)) : + φ[i][k].1 = φ[j][l].1 ∧ + z = realPoint (positiveVariablePoint φ d (.inl (.inl φ[i][k].1))) := by + have hne' : sourceEdgeIndex φ i k ≠ sourceEdgeIndex φ j l := by + intro he + have ho := (List.idxOf_inj (sourceOccurrence_mem φ hi hk)).mp he + exact hne (congrArg (fun o : Occurrence ℕ => (o.1, o.2.1)) ho) + rw [positiveExterior, support_translate] at hz hw + obtain ⟨x, hx, rfl⟩ := hz + obtain ⟨y, hy, hyx⟩ := hw + have hy' : y = x := add_right_cancel hyx + subst y + obtain ⟨heq, hxq⟩ := exteriorRoute_contacts hd (sourceEdgeIndex_lt φ hi hk) + (sourceEdgeIndex_lt φ hj hl) hne' (by decide : 0 < positiveRadius) + (by decide : positiveRadius < positiveScale) hx hy + have hedge₁ := sourceEdgeIndex_edge φ hi hk + have hedge₂ := sourceEdgeIndex_edge φ hj hl + rw [List.getD_eq_getElem _ _ (sourceEdgeIndex_lt φ hi hk)] at hedge₁ + rw [List.getD_eq_getElem _ _ (sourceEdgeIndex_lt φ hj hl)] at hedge₂ + rw [hedge₁, hedge₂] at heq + have hmem₁ : φ[i][k].1 ∈ φ.vars := CNF.mem_vars.mpr + ⟨φ[i], List.getElem_mem hi, φ[i][k].2, List.getElem_mem hk⟩ + have hmem₂ : φ[j][l].1 ∈ φ.vars := CNF.mem_vars.mpr + ⟨φ[j], List.getElem_mem hj, φ[j][l].2, List.getElem_mem hl⟩ + refine ⟨(List.idxOf_inj (CNF.mem_distinctVars.mpr hmem₁)).mp heq, ?_⟩ + rw [positiveVariablePoint_source φ d hmem₁, realPoint_add] + dsimp only + rw [hxq] + congr 2 + rw [sourceFinish, sourceEdgeIndex_edge φ hi hk] + +/-- Source-placeholder metadata for an actual local row. -/ +theorem positiveIncidence_source_lookup {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile φ[i]))) + {e : LocalIncidence} (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) {j : ℕ} + (hget : (profileVariables (clauseProfile φ[i])).getD e.2.1 (.inl (.inl 0)) = + .inl (.inl j)) : + j < φ[i].length ∧ j < 3 ∧ + e.2.1 ∈ (profilePrefixGeometry (clauseProfile φ[i])).terminalVariables ∧ + positiveIncidenceVariable φ i φ[i] e = .inl (.inl (φ[i].getD j (0, true)).1) := by + have hends := collaredProfile_incidence_endpoints _ h (clausePorts_spec hd hi).1 he + have hv : e.2.1 < (profilePrefixGeometry (clauseProfile φ[i])).variablePositions.length := by + simpa only [collaredPrefixGeometry, List.length_map, List.length_zipIdx] using hends.2.1 + have hm := profile_source_lookup _ hv hget + have hj : j < φ[i].length := lt_of_lt_of_le hm.1 (profileSource_length_le φ[i]) + refine ⟨hj, hm.2.1, hm.2.2.2.1, ?_⟩ + unfold positiveIncidenceVariable + rw [hget] + rfl + +/-- Two distinct actual local rows cannot share a source placeholder. -/ +theorem clauseGeometry_shared_variable_auxiliary {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile φ[i]))) + {e f : LocalIncidence} (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) + (hf : f ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) (hne : e ≠ f) + (hv : e.2.1 = f.2.1) : + ∀ j, (profileVariables (clauseProfile φ[i])).getD e.2.1 (.inl (.inl 0)) ≠ + .inl (.inl j) := by + intro j hj + have hmeta := positiveIncidence_source_lookup hd hi h he hj + have ht : f.2.1 ∈ (profilePrefixGeometry (clauseProfile φ[i])).terminalVariables := + hv ▸ hmeta.2.2.1 + have hn := collaredProfile_other_terminal_not_mem_support _ h (clausePorts_spec hd hi).1 + he hf hne ht + have hend := (collaredProfile_incidence_endpoints _ h (clausePorts_spec hd hi).1 he).2.2.2 + exact hn (Or.inl ⟨_, List.mem_of_getLast? hend, congrArg realPoint + (congrArg (LocalGeometry.variablePosition (clauseGeometry φ d i φ[i])) hv).symm⟩) + +/-- The last local point of a private incidence is its actual global variable vertex. -/ +theorem positiveIncidenceVariable_auxiliary_point {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile φ[i]))) + {e : LocalIncidence} (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) + (ha : ∀ n, (profileVariables (clauseProfile φ[i])).getD e.2.1 (.inl (.inl 0)) ≠ + Sum.inl (Sum.inl n)) : + positiveVariablePoint φ d (positiveIncidenceVariable φ i φ[i] e) = + positiveBoxOrigin φ d i + (clauseGeometry φ d i φ[i]).variablePosition e.2.1 := by + have hends := collaredProfile_incidence_endpoints _ h (clausePorts_spec hd hi).1 he + have hlen : (clauseGeometry φ d i φ[i]).variablePositions.length = + (profileVariables (clauseProfile φ[i])).length := by + simp only [clauseGeometry, collaredPrefixGeometry, List.length_map, List.length_zipIdx, + profilePrefixGeometry, LocalGeometry.toPrefix, profileGeometry_variable_length] + exact positiveVariablePoint_profile_auxiliary_getD φ d + (List.mem_zipIdx_iff_getElem?.mpr (List.getElem?_eq_some_iff.mpr ⟨hi, rfl⟩)) + (by + have hvi := hends.2.1 + change e.2.1 < (clauseGeometry φ d i φ[i]).variablePositions.length at hvi + omega) ha + +private theorem clauseGeometry_source_lookup_injective {φ : CNF ℕ} + {d : Graph.OrthogonalDrawing} + (_hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile φ[i]))) + {e f : LocalIncidence} (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) + (hf : f ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) {j : ℕ} + (heget : (profileVariables (clauseProfile φ[i])).getD e.2.1 (.inl (.inl 0)) = + .inl (.inl j)) + (hfget : (profileVariables (clauseProfile φ[i])).getD f.2.1 (.inl (.inl 0)) = + .inl (.inl j)) : e = f := by + obtain ⟨a, ha, rfl⟩ := List.mem_map.mp he + obtain ⟨b, hb, rfl⟩ := List.mem_map.mp hf + have hma := profile_source_lookup _ (LocalGeometry.prefix_incidence_endpoints h ha).2.1 heget + have hmb := profile_source_lookup _ (LocalGeometry.prefix_incidence_endpoints h hb).2.1 hfget + have hab := profile_prefix_terminal_unique _ a ha hma.2.2.2.1 b hb + (hma.2.2.1.trans hmb.2.2.1.symm) + subst b + rfl + +/-- All continuous contacts between distinct emitted incidences are their actual common +clause or globally shared variable endpoints. -/ +theorem positiveIncidenceRoute_contacts {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i j : ℕ} + (hi : i < φ.length) (hj : j < φ.length) + (h : ∀ k : ℕ, ∀ hk : k < φ.length, + LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile φ[k]))) + {e f : LocalIncidence} (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) + (hf : f ∈ (clauseGeometry φ d j φ[j]).incidenceRoutes) (hne : (i, e) ≠ (j, f)) + {z : ℝ × ℝ} (hz : z ∈ support (positiveIncidenceRoute φ d i φ[i] e)) + (hw : z ∈ support (positiveIncidenceRoute φ d j φ[j] f)) : + (i = j ∧ e.1 = f.1 ∧ + z = realPoint (positiveBoxOrigin φ d i + (clauseGeometry φ d i φ[i]).clausePosition e.1)) ∨ + (positiveIncidenceVariable φ i φ[i] e = positiveIncidenceVariable φ j φ[j] f ∧ + z = realPoint (positiveVariablePoint φ d (positiveIncidenceVariable φ i φ[i] e))) := by + rw [mem_support_positiveIncidenceRoute_iff hd hi (h i hi) he] at hz + rw [mem_support_positiveIncidenceRoute_iff hd hj (h j hj) hf] at hw + rcases hz with hz | ⟨k, hgetk, hz⟩ <;> rcases hw with hw | ⟨l, hgetl, hw⟩ + · by_cases hij : i = j + · subst j + have hef : e ≠ f := fun hef => hne (Prod.ext rfl hef) + obtain ⟨q, hq, hzq⟩ := positiveStem_contacts hd hi φ[i] (h i hi) he hf hef hz hw + rw [LocalGeometry.commonEndpoints, List.mem_append] at hq + rcases hq with hq | hq + · split_ifs at hq with hc + · simp only [List.mem_singleton] at hq + exact Or.inl ⟨rfl, hc, hq ▸ hzq⟩ + · exact False.elim (List.not_mem_nil hq) + · split_ifs at hq with hv + · simp only [List.mem_singleton] at hq + have ha := clauseGeometry_shared_variable_auxiliary hd hi (h i hi) he hf hef hv + refine Or.inr ⟨?_, ?_⟩ + · unfold positiveIncidenceVariable + rw [hv] + · rw [positiveIncidenceVariable_auxiliary_point hd hi (h i hi) he ha] + exact hq ▸ hzq + · exact False.elim (List.not_mem_nil hq) + · exact False.elim (Set.disjoint_left.mp + (positiveStem_different_boxes_disjoint hd hi hj hij φ[i] φ[j] he hf) hz hw) + · have hm := positiveIncidence_source_lookup hd hj (h j hj) hf hgetl + by_cases hij : i = j + · subst j + have hef : e ≠ f := fun hef => hne (Prod.ext rfl hef) + exact False.elim (Set.disjoint_left.mp + (positiveStem_other_exterior_disjoint hd hi (h i hi) he hf hef hgetl) hz hw) + · exact False.elim (Set.disjoint_left.mp + (positiveStem_foreign_exterior_disjoint hd hi hj hm.1 hij φ[i] he) hz hw) + · have hm := positiveIncidence_source_lookup hd hi (h i hi) he hgetk + by_cases hij : i = j + · subst j + have hfe : f ≠ e := fun hfe => hne (Prod.ext rfl hfe.symm) + exact False.elim (Set.disjoint_left.mp + (positiveStem_other_exterior_disjoint hd hi (h i hi) hf he hfe hgetk) hw hz) + · exact False.elim (Set.disjoint_left.mp + (positiveStem_foreign_exterior_disjoint hd hj hi hm.1 (Ne.symm hij) φ[j] hf) hw hz) + · have hmk := positiveIncidence_source_lookup hd hi (h i hi) he hgetk + have hml := positiveIncidence_source_lookup hd hj (h j hj) hf hgetl + have hslots : (i, k) ≠ (j, l) := by + intro heq + have hij : i = j := congrArg Prod.fst heq + have hkl : k = l := congrArg Prod.snd heq + subst j + subst l + have hef := clauseGeometry_source_lookup_injective hd hi (h i hi) he hf hgetk hgetl + exact hne (Prod.ext rfl hef) + obtain ⟨hvars, hzvar⟩ := positiveExterior_contacts hd hi hj hmk.1 hml.1 hslots hz hw + refine Or.inr ⟨?_, ?_⟩ + · rw [hmk.2.2.2, hml.2.2.2, List.getD_eq_getElem _ _ hmk.1, + List.getD_eq_getElem _ _ hml.1, hvars] + · rw [hmk.2.2.2, List.getD_eq_getElem _ _ hmk.1] + exact hzvar + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingRoutes.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingRoutes.lean new file mode 100644 index 000000000..d0c2ba543 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingRoutes.lean @@ -0,0 +1,471 @@ +/- +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.ExactOne.DrawingEndpoints +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesCollar +import Mathlib.Data.List.GetD +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.NormNum + +/-! +# Continuous local and exterior routes of the positive drawing compiler + +Local prefixes lie in their closed clause boxes. Their inherited exterior routes touch those +boxes at the prescribed source ports only. These full-real-support facts justify the actual +route concatenations, including shared source variables and parallel source incidences. + +## Main definitions + +* `positiveStem`: the translated local part of an actual emitted incidence. + +## Main results + +* `positiveStem_in_box`, `positiveExterior_box_contact`: clause-box support isolation. +* `positiveIncidenceRoute_regular`: proper orthogonality and continuous simplicity of actual routes. +* `positiveIncidenceRoute_join_endpoints`: exact endpoint identity for additive route costs. +* `positiveStem_contacts`: finite local prefixes meet only at their actual shared endpoints. + +## References + +These finite drawing-composition and continuous-support proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +private theorem collarRoute_points_bounds (reflected : Bool) {r : Route} + (hr : ∀ q ∈ r, 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000) : + ∀ q ∈ collarRoute reflected r, + 0 ≤ q.1 ∧ q.1 ≤ 540000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 540000 := by + intro q hq + rw [collarRoute_eq_map] at hq + obtain ⟨a, ha, rfl⟩ := List.mem_map.mp hq + have hb := hr a ha + cases reflected <;> dsimp [collarPoint, collarOffset, mirrorPoint] <;> omega + +private theorem collarPrefixRoute_points_bounds {s : PortAssignment} (hs : s.Distinct) + (i : Fin 3) {r : Route} + (hr : ∀ q ∈ r, 0 ≤ q.1 ∧ q.1 ≤ 180000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 180000) : + ∀ q ∈ collarPrefixRoute s i r, + 0 ≤ q.1 ∧ q.1 ≤ 540000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 540000 := by + intro q hq + rw [collarPrefixRoute, List.mem_append] at hq + rcases hq with hq | hq + · exact collarRoute_points_bounds _ hr q hq + · have hq' := List.mem_reverse.mp (List.mem_of_mem_tail hq) + exact (((collar_check s hs).2 i).2.2.2.2.1) q hq' + +/-- All listed points of an actual collared local incidence lie in the common clause square. +-/ +theorem clauseGeometry_incidence_points_bounds {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) (c : Clause ℕ) {e : LocalIncidence} + (he : e ∈ (clauseGeometry φ d i c).incidenceRoutes) : + ∀ q ∈ e.2.2, 0 ≤ q.1 ∧ q.1 ≤ 540000 ∧ 0 ≤ q.2 ∧ q.2 ≤ 540000 := by + have hs := (clausePorts_spec hd hi).1 + obtain ⟨f, hf, rfl⟩ := List.mem_map.mp he + dsimp only + split_ifs with ht + · exact collarPrefixRoute_points_bounds hs _ + (profile_prefix_incidence_points_bounds (clauseProfile c) f hf) + · exact collarRoute_points_bounds _ + (profile_prefix_incidence_points_bounds (clauseProfile c) f hf) + +/-- A common translation preserves membership in a closed local square. -/ +theorem inPointBox_translate_iff (c v : Point) (radius : ℕ) (p : ℝ × ℝ) : + inPointBox (c + v) radius (p + realPoint v) ↔ inPointBox c radius p := by + simp only [inPointBox, Prod.fst_add, Prod.snd_add, realPoint, Int.cast_add] + constructor <;> intro h + all_goals + constructor + · linarith [h.1] + constructor + · linarith [h.2.1] + constructor <;> linarith [h.2.2.1, h.2.2.2] + +/-- The translated local prefix used by an actual output incidence. -/ +def positiveStem (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) (i : ℕ) + (e : LocalIncidence) : Route := translate (positiveBoxOrigin φ d i) e.2.2 + +/-- Every real point of a local prefix lies in its source clause's closed replacement box. -/ +theorem positiveStem_in_box {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) (c : Clause ℕ) {e : LocalIncidence} + (he : e ∈ (clauseGeometry φ d i c).incidenceRoutes) + {p : ℝ × ℝ} (hp : p ∈ support (positiveStem φ d i e)) : + inPointBox (positiveBoxOrigin φ d i + positiveTranslation) positiveRadius p := by + rw [positiveStem, support_translate] at hp + obtain ⟨q, hq, rfl⟩ := hp + rw [add_comm (positiveBoxOrigin φ d i) positiveTranslation, inPointBox_translate_iff] + apply support_inPointBox_of_points (r := e.2.2) ?_ hq + intro a ha + have hb := clauseGeometry_incidence_points_bounds hd hi c he a ha + norm_num [inPointBox, positiveTranslation, positiveRadius, realPoint] + exact_mod_cast hb + +/-- The normalized inherited route remains properly orthogonal and continuously simple. -/ +theorem positiveExterior_regular {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i j : ℕ} + (hi : i < φ.length) (hj : j < φ[i].length) : + IsOrthogonal (positiveExterior φ d i j) ∧ IsSimple (positiveExterior φ d i j) := by + have h := exteriorRoute_regular hd (sourceEdgeIndex_lt φ hi hj) + (by decide : positiveRadius < positiveScale) + exact ⟨(isOrthogonal_translate_iff _ _).2 h.1, (isSimple_translate_iff _ _).2 h.2⟩ + +/-- The actual translated exterior route meets its own closed clause box only at the selected +local collar port. -/ +theorem positiveExterior_box_contact {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) (j : Fin 3) (hj : j.val < φ[i].length) + {p : ℝ × ℝ} (hp : p ∈ support (positiveExterior φ d i j)) + (hb : inPointBox (positiveBoxOrigin φ d i + positiveTranslation) positiveRadius p) : + p = realPoint (positiveBoxOrigin φ d i + collarOuterPort ((clausePorts φ d i).side j)) := by + rw [positiveExterior, support_translate] at hp + obtain ⟨q, hq, rfl⟩ := hp + rw [inPointBox_translate_iff] at hb + have hstart : sourceStart (SAT.incidenceGraph φ) d (sourceEdgeIndex φ i j) = + Graph.drawingPosition (SAT.incidenceGraph φ) d (φ.distinctVars.length + i) := by + rw [sourceStart, sourceEdgeIndex_edge φ hi hj] + have hb' : inPointBox + (positiveScale • sourceStart (SAT.incidenceGraph φ) d (sourceEdgeIndex φ i j)) + positiveRadius q := by simpa only [positiveBoxOrigin, hstart] using hb + have hport := exteriorRoute_box_contact hd (sourceEdgeIndex_lt φ hi hj) + (by decide : positiveRadius < positiveScale) hq hb' + have hhead := positiveExterior_head hd hi j hj + unfold positiveExterior translate at hhead + rw [List.head?_map, (exteriorRoute_endpoints hd (sourceEdgeIndex_lt φ hi hj) + positiveScale positiveRadius).1, Option.map_some] at hhead + have hpoint := Option.some.inj hhead + calc + q + realPoint positiveTranslation = + realPoint (exteriorPort positiveScale positiveRadius (SAT.incidenceGraph φ) d + (sourceEdgeIndex φ i j)) + realPoint positiveTranslation := + congrArg (· + realPoint positiveTranslation) hport + _ = realPoint (exteriorPort positiveScale positiveRadius (SAT.incidenceGraph φ) d + (sourceEdgeIndex φ i j) + positiveTranslation) := (realPoint_add _ _).symm + _ = _ := congrArg realPoint hpoint + +private theorem head_join_of_some {q r : Route} {a : Point} + (h : q.head? = some a) : (q ++ r).head? = some a := by + obtain ⟨t, rfl⟩ := List.head?_eq_some_iff.mp h + rfl + +/-- A collared local incidence retains its clause endpoint and ends at its moved variable. +The indices remain valid in the actual collared tables. -/ +theorem collaredProfile_incidence_endpoints (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e : LocalIncidence} + (he : e ∈ (collaredPrefixGeometry (profilePrefixGeometry p) s).incidenceRoutes) : + e.1 < (collaredPrefixGeometry (profilePrefixGeometry p) s).clausePositions.length ∧ + e.2.1 < (collaredPrefixGeometry (profilePrefixGeometry p) s).variablePositions.length ∧ + e.2.2.head? = some ((collaredPrefixGeometry (profilePrefixGeometry p) s).clausePosition e.1) ∧ + e.2.2.getLast? = + some ((collaredPrefixGeometry (profilePrefixGeometry p) s).variablePosition e.2.1) := by + obtain ⟨f, hf, rfl⟩ := List.mem_map.mp he + have hfend := LocalGeometry.prefix_incidence_endpoints h hf + refine ⟨by simpa only [collaredPrefixGeometry, List.length_map] using hfend.1, + by simpa only [collaredPrefixGeometry, List.length_map, List.length_zipIdx] using hfend.2.1, + ?_, ?_⟩ + · rw [collared_clausePoint _ _ hfend.1] + have hhead : (collarRoute (collarReflected s) f.2.2).head? = + some (collarPoint (collarReflected s) ((profilePrefixGeometry p).clausePosition f.1)) := by + rw [collarRoute_eq_map, List.head?_map, hfend.2.2.1, Option.map_some] + dsimp only + split_ifs <;> first | exact head_join_of_some hhead | exact hhead + · rw [collared_variablePoint _ _ hfend.2.1] + dsimp only + split_ifs with ht + · have hend := hfend.2.2.2 + rw [profile_prefix_terminal_endpoint p f.2.1 ht] at hend + have hleft : (collarRoute (collarReflected s) f.2.2).getLast? = + some (collarInnerPort (collarReflected s) + (terminalIndex (profilePrefixGeometry p) f.2.1)) := by + rw [collarRoute_eq_map, List.getLast?_map, hend, Option.map_some, + collarPoint_innerTemplatePort] + have hright := ((collar_check s hs).2 (terminalIndex (profilePrefixGeometry p) f.2.1)).2.1 + rw [collarPrefixRoute, last_join hleft (by simpa only [List.head?_reverse] using hright), + List.getLast?_reverse] + exact ((collar_check s hs).2 (terminalIndex (profilePrefixGeometry p) f.2.1)).1 + · rw [collarRoute_eq_map, List.getLast?_map, hfend.2.2.2, Option.map_some] + +/-- The actual translated local incidence is proper and simple, with its prescribed endpoints. +-/ +theorem positiveStem_regular {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) (c : Clause ℕ) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile c))) + {e : LocalIncidence} (he : e ∈ (clauseGeometry φ d i c).incidenceRoutes) : + IsOrthogonal (positiveStem φ d i e) ∧ IsSimple (positiveStem φ d i e) ∧ + (positiveStem φ d i e).head? = + some (positiveBoxOrigin φ d i + (clauseGeometry φ d i c).clausePosition e.1) ∧ + (positiveStem φ d i e).getLast? = + some (positiveBoxOrigin φ d i + (clauseGeometry φ d i c).variablePosition e.2.1) := by + have hs := (clausePorts_spec hd hi).1 + have hept := collaredProfile_incidence_endpoints (clauseProfile c) h hs he + refine ⟨(isOrthogonal_translate_iff _ _).2 + (collaredProfile_incidence_orthogonal _ h hs he), + (isSimple_translate_iff _ _).2 (collaredProfile_incidence_isSimple _ h hs he), ?_, ?_⟩ + · unfold positiveStem translate + rw [List.head?_map, hept.2.2.1, Option.map_some] + exact congrArg some (add_comm _ _) + · unfold positiveStem translate + rw [List.getLast?_map, hept.2.2.2, Option.map_some] + exact congrArg some (add_comm _ _) + +/-- A local source-placeholder lookup identifies its unique terminal slot, including the +actual unmodded source position. -/ +theorem profile_source_lookup (p : ClauseProfile) {v j : ℕ} + (hv : v < (profilePrefixGeometry p).variablePositions.length) + (hget : (profileVariables p).getD v (.inl (.inl 0)) = .inl (.inl j)) : + j < (profileSource p).length ∧ j < 3 ∧ + v = (profileVariables p).idxOf (.inl (.inl j)) ∧ + v ∈ (profilePrefixGeometry p).terminalVariables ∧ + (terminalIndex (profilePrefixGeometry p) v).val = j := by + have hlen : (profilePrefixGeometry p).variablePositions.length = + (profileVariables p).length := by + simpa only [profilePrefixGeometry, LocalGeometry.toPrefix, List.length_map, + List.length_zipIdx] using profileGeometry_variable_length p + have hv' : v < (profileVariables p).length := by omega + have hget' : (profileVariables p)[v] = .inl (.inl j) := by + simpa only [List.getD_eq_getElem _ _ hv'] using hget + have hm : Sum.inl (Sum.inl j) ∈ profileVariables p := hget' ▸ List.getElem_mem hv' + have hj := profileVariables_scope p _ hm + have hp : (profileSource p).length ≤ 3 := by fin_cases p <;> decide +kernel + have hj3 : j < 3 := by omega + have hidx : v = (profileVariables p).idxOf (.inl (.inl j)) := by + rw [← hget'] + exact ((CNF.nodup_distinctVars (profileFormula p)).idxOf_getElem v hv').symm + have ht := profileSource_terminal_index p ⟨j, hj3⟩ hj + refine ⟨hj, hj3, hidx, ?_, ?_⟩ + · rw [hidx] + exact ht.2.1 + · change (profileGeometry p).terminalVariables.idxOf v % 3 = j + rw [hidx, ht.2.2, Nat.mod_eq_of_lt hj3] + +/-- Every actual emitted incidence is properly orthogonal and continuously simple. +Source placeholders are spliced at the sole contact of their clause box with the inherited +route; auxiliary incidences retain their finite prefix. -/ +theorem positiveIncidenceRoute_regular {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile φ[i]))) + {e : LocalIncidence} (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) : + IsOrthogonal (positiveIncidenceRoute φ d i φ[i] e) ∧ + IsSimple (positiveIncidenceRoute φ d i φ[i] e) := by + have hstem := positiveStem_regular hd hi φ[i] h he + have hends := collaredProfile_incidence_endpoints (clauseProfile φ[i]) h + (clausePorts_spec hd hi).1 he + have hv : e.2.1 < (profilePrefixGeometry (clauseProfile φ[i])).variablePositions.length := by + simpa only [collaredPrefixGeometry, List.length_map, List.length_zipIdx] using hends.2.1 + unfold positiveIncidenceRoute + split + next j hj => + have hm := profile_source_lookup _ hv hj + let j' : Fin 3 := ⟨j, hm.2.1⟩ + have hjSource : j < φ[i].length := + lt_of_lt_of_le hm.1 (profileSource_length_le φ[i]) + have hlast : (positiveStem φ d i e).getLast? = + some (positiveBoxOrigin φ d i + collarOuterPort ((clausePorts φ d i).side j')) := by + rw [hstem.2.2.2, hm.2.2.1, clauseGeometry_source_point φ d i φ[i] j' hm.1] + have hhead := positiveExterior_head hd hi j' hjSource + have hext := positiveExterior_regular hd hi hjSource + exact ⟨isOrthogonal_join hlast hhead hstem.1 hext.1, + isSimple_join hlast hhead hstem.2.1 hext.2 (by + intro z hz hw + exact positiveExterior_box_contact hd hi j' hjSource hw + (positiveStem_in_box hd hi φ[i] he hz))⟩ + next => exact ⟨hstem.1, hstem.2.1⟩ + +/-- Collaring preserves exactly the allowed local clause and auxiliary endpoint contacts. +Distinct incidences never share one of the removed source placeholders. -/ +theorem collaredProfile_routes_contact (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e f : LocalIncidence} + (he : e ∈ (collaredPrefixGeometry (profilePrefixGeometry p) s).incidenceRoutes) + (hf : f ∈ (collaredPrefixGeometry (profilePrefixGeometry p) s).incidenceRoutes) + (hne : e ≠ f) {z : ℝ × ℝ} (hz : z ∈ support e.2.2) (hw : z ∈ support f.2.2) : + ∃ q ∈ (collaredPrefixGeometry (profilePrefixGeometry p) s).commonEndpoints e f, + z = realPoint q := by + obtain ⟨a, ha, rfl⟩ := List.mem_map.mp he + obtain ⟨b, hb, rfl⟩ := List.mem_map.mp hf + have hab : a ≠ b := by intro heq; subst b; exact hne rfl + have hz' : z ∈ support (profileCollarRoute p s a) := hz + have hw' : z ∈ support (profileCollarRoute p s b) := hw + obtain ⟨q, hq, hzq⟩ := profileCollarRoute_contact p h hs ha hb hab hz' hw' + rw [LocalGeometry.commonEndpoints, List.mem_append] at hq + rcases hq with hq | hq + · split_ifs at hq with hc + · simp only [List.mem_singleton] at hq + subst q + refine ⟨collarPoint (collarReflected s) ((profilePrefixGeometry p).clausePosition a.1), + ?_, ?_⟩ + · apply List.mem_append_left + simp only [hc, ↓reduceIte, List.mem_singleton] + exact (collared_clausePoint _ _ (LocalGeometry.prefix_incidence_endpoints h hb).1).symm + · exact hzq.trans (realPoint_collarPoint _ _).symm + · exact False.elim (List.not_mem_nil hq) + · split_ifs at hq with hv + · simp only [List.mem_singleton] at hq + subst q + have ht := profile_prefix_shared_variable_not_terminal p ha hb hab hv + refine ⟨collarPoint (collarReflected s) ((profilePrefixGeometry p).variablePosition a.2.1), + ?_, ?_⟩ + · apply List.mem_append_right + simp only [hv, ↓reduceIte, List.mem_singleton] + rw [collared_variablePoint _ _ (LocalGeometry.prefix_incidence_endpoints h hb).2.1, + ite_eq_right (hv ▸ ht)] + · exact hzq.trans (realPoint_collarPoint _ _).symm + · exact False.elim (List.not_mem_nil hq) + +/-- Different local prefixes in one clause box meet only at their common actual endpoints. -/ +theorem positiveStem_contacts {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) (c : Clause ℕ) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile c))) + {e f : LocalIncidence} (he : e ∈ (clauseGeometry φ d i c).incidenceRoutes) + (hf : f ∈ (clauseGeometry φ d i c).incidenceRoutes) (hne : e ≠ f) + {z : ℝ × ℝ} (hz : z ∈ support (positiveStem φ d i e)) + (hw : z ∈ support (positiveStem φ d i f)) : + ∃ q ∈ (clauseGeometry φ d i c).commonEndpoints e f, + z = realPoint (positiveBoxOrigin φ d i + q) := by + rw [positiveStem, support_translate] at hz hw + obtain ⟨x, hx, rfl⟩ := hz + obtain ⟨y, hy, hyx⟩ := hw + have hy' : y = x := add_right_cancel hyx + subst y + obtain ⟨q, hq, hxq⟩ := collaredProfile_routes_contact _ h (clausePorts_spec hd hi).1 + he hf hne hx hy + refine ⟨q, hq, ?_⟩ + rw [hxq] + change realPoint q + realPoint (positiveBoxOrigin φ d i) = _ + rw [← realPoint_add, add_comm q] + +/-- Actual collared routes avoid every other local variable, including exposed source ports. +-/ +theorem collaredProfile_variable_not_mem_support (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e : LocalIncidence} + (he : e ∈ (collaredPrefixGeometry (profilePrefixGeometry p) s).incidenceRoutes) + (v : Fin (profilePrefixGeometry p).variablePositions.length) (hv : v.val ≠ e.2.1) : + realPoint ((collaredPrefixGeometry (profilePrefixGeometry p) s).variablePosition v) ∉ + support e.2.2 := by + obtain ⟨a, ha, rfl⟩ := List.mem_map.mp he + rw [collared_variablePoint _ _ v.isLt] + by_cases ht : v.val ∈ (profilePrefixGeometry p).terminalVariables + · rw [ite_eq_left ht] + exact profileCollarRoute_source_not_mem_support p h hs ha ht hv + · rw [ite_eq_right ht] + exact profileCollarRoute_auxiliary_not_mem_support p h hs ha v ht hv + +/-- Actual collared routes avoid every other local clause vertex. -/ +theorem collaredProfile_clause_not_mem_support (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e : LocalIncidence} + (he : e ∈ (collaredPrefixGeometry (profilePrefixGeometry p) s).incidenceRoutes) + (j : Fin (profilePrefixGeometry p).clausePositions.length) (hj : j.val ≠ e.1) : + realPoint ((collaredPrefixGeometry (profilePrefixGeometry p) s).clausePosition j) ∉ + support e.2.2 := by + obtain ⟨a, ha, rfl⟩ := List.mem_map.mp he + rw [collared_clausePoint _ _ j.isLt] + exact profileCollarRoute_clause_not_mem_support p h hs ha j hj + +/-- Distinct local incidences cannot contact the source port of one another. -/ +theorem collaredProfile_other_terminal_not_mem_support (p : ClauseProfile) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry p)) + {s : PortAssignment} (hs : s.Distinct) {e f : LocalIncidence} + (he : e ∈ (collaredPrefixGeometry (profilePrefixGeometry p) s).incidenceRoutes) + (hf : f ∈ (collaredPrefixGeometry (profilePrefixGeometry p) s).incidenceRoutes) + (hne : e ≠ f) (ht : f.2.1 ∈ (profilePrefixGeometry p).terminalVariables) : + realPoint ((collaredPrefixGeometry (profilePrefixGeometry p) s).variablePosition f.2.1) ∉ + support e.2.2 := by + obtain ⟨a, ha, rfl⟩ := List.mem_map.mp he + obtain ⟨b, hb, rfl⟩ := List.mem_map.mp hf + have hv : b.2.1 ≠ a.2.1 := by + intro hv + have hab := profile_prefix_terminal_unique p b hb ht a ha hv + subst a + exact hne rfl + exact collaredProfile_variable_not_mem_support p h hs (List.mem_map_of_mem ha) + ⟨b.2.1, profile_prefix_terminal_lt p _ ht⟩ hv + +/-- A translated local incidence avoids the exposed endpoint of every other incidence. -/ +theorem positiveStem_other_terminal_not_mem_support {φ : CNF ℕ} + {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) (c : Clause ℕ) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile c))) + {e f : LocalIncidence} (he : e ∈ (clauseGeometry φ d i c).incidenceRoutes) + (hf : f ∈ (clauseGeometry φ d i c).incidenceRoutes) (hne : e ≠ f) + (ht : f.2.1 ∈ (profilePrefixGeometry (clauseProfile c)).terminalVariables) : + realPoint (positiveBoxOrigin φ d i + (clauseGeometry φ d i c).variablePosition f.2.1) ∉ + support (positiveStem φ d i e) := by + have hn := collaredProfile_other_terminal_not_mem_support _ h (clausePorts_spec hd hi).1 + he hf hne ht + intro hz + rw [positiveStem, support_translate] at hz + obtain ⟨x, hx, hxp⟩ := hz + have hx' : x = realPoint ((clauseGeometry φ d i c).variablePosition f.2.1) := by + apply add_right_cancel (b := realPoint (positiveBoxOrigin φ d i)) + simpa only [realPoint_add, add_comm (realPoint (positiveBoxOrigin φ d i))] using hxp + exact hn (hx' ▸ hx) + +/-- Inside one clause box, an inherited exterior is disjoint from every other local prefix. +The shared clause center has already been cut away. -/ +theorem positiveStem_other_exterior_disjoint {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile φ[i]))) + {e f : LocalIncidence} (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) + (hf : f ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) (hne : e ≠ f) {j : ℕ} + (hget : (profileVariables (clauseProfile φ[i])).getD f.2.1 (.inl (.inl 0)) = + .inl (.inl j)) : + Disjoint (support (positiveStem φ d i e)) (support (positiveExterior φ d i j)) := by + have hends := collaredProfile_incidence_endpoints _ h (clausePorts_spec hd hi).1 hf + have hv : f.2.1 < (profilePrefixGeometry (clauseProfile φ[i])).variablePositions.length := by + simpa only [collaredPrefixGeometry, List.length_map, List.length_zipIdx] using hends.2.1 + have hm := profile_source_lookup _ hv hget + let j' : Fin 3 := ⟨j, hm.2.1⟩ + have hj : j < φ[i].length := lt_of_lt_of_le hm.1 (profileSource_length_le φ[i]) + rw [Set.disjoint_left] + intro z hz hw + have hzport := positiveExterior_box_contact hd hi j' hj hw + (positiveStem_in_box hd hi φ[i] he hz) + have hpoint : (clauseGeometry φ d i φ[i]).variablePosition f.2.1 = + collarOuterPort ((clausePorts φ d i).side j') := by + rw [hm.2.2.1, clauseGeometry_source_point φ d i φ[i] j' hm.1] + apply positiveStem_other_terminal_not_mem_support hd hi φ[i] h he hf hne hm.2.2.2.1 + rw [hpoint, ← hzport] + exact hz + +/-- A source incidence's finite stem and inherited exterior have the same actual integer +endpoint. This identity also supports exact wire-length and segment-count bookkeeping. -/ +theorem positiveIncidenceRoute_join_endpoints {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i : ℕ} + (hi : i < φ.length) + (h : LocalGeometry.PrefixCheck (profilePrefixGeometry (clauseProfile φ[i]))) + {e : LocalIncidence} (he : e ∈ (clauseGeometry φ d i φ[i]).incidenceRoutes) {j : ℕ} + (hget : (profileVariables (clauseProfile φ[i])).getD e.2.1 (.inl (.inl 0)) = + .inl (.inl j)) : + ∃ q : Point, (positiveStem φ d i e).getLast? = some q ∧ + (positiveExterior φ d i j).head? = some q := by + have hends := collaredProfile_incidence_endpoints _ h (clausePorts_spec hd hi).1 he + have hv : e.2.1 < (profilePrefixGeometry (clauseProfile φ[i])).variablePositions.length := by + simpa only [collaredPrefixGeometry, List.length_map, List.length_zipIdx] using hends.2.1 + have hm := profile_source_lookup _ hv hget + let j' : Fin 3 := ⟨j, hm.2.1⟩ + have hj : j < φ[i].length := lt_of_lt_of_le hm.1 (profileSource_length_le φ[i]) + refine ⟨positiveBoxOrigin φ d i + collarOuterPort ((clausePorts φ d i).side j'), ?_, + positiveExterior_head hd hi j' hj⟩ + rw [(positiveStem_regular hd hi φ[i] h he).2.2.2, hm.2.2.1, + clauseGeometry_source_point φ d i φ[i] j' hm.1] + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingSource.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingSource.lean new file mode 100644 index 000000000..3e8b173d2 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingSource.lean @@ -0,0 +1,372 @@ +/- +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.Graph.OrthogonalDrawing +public import Complexity.Foundations.Geometry.Orthogonal.Directions +public import Complexity.Foundations.Geometry.Orthogonal.Reverse +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Nodup + +/-! +# Exterior routes for clause-box replacement + +An incidence edge is normalized from its second endpoint (the clause vertex) to its first +endpoint (the variable vertex), independently of the supplied route orientation. Dilation and +initial trimming provide an exterior route with a single contact with its clause box. The +normalization changes neither route support nor geometric costs. + +## Main definitions + +* `sourceRoute`, `sourceDirection`: the normalized source incidence and its exit direction. +* `exteriorRoute`, `exteriorPort`: the scaled, trimmed incidence and its designated box port. + +## Main results + +* `sourceRoute_endpoints`, `sourceRoute_regular`: normalized source-route correctness. +* `sourceDirection_ne`: distinct incidences at a clause use distinct cardinal directions. +* `exteriorRoute_box_contact`: the exterior route meets its own closed box only at the port. +* `exteriorRoute_avoids_box`: nonincident source vertices have disjoint replacement boxes. + +## References + +The source normalization and geometric assembly proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Orthogonal + +/-- The clause endpoint of a listed incidence, with a fixed fallback for invalid indices. -/ +def sourceStart (G : Graph) (d : Graph.OrthogonalDrawing) (i : ℕ) : Point := + Graph.drawingPosition G d (G.2.getD i (0, 0)).2 + +/-- The variable endpoint of a listed incidence, with a fixed fallback for invalid indices. -/ +def sourceFinish (G : Graph) (d : Graph.OrthogonalDrawing) (i : ℕ) : Point := + Graph.drawingPosition G d (G.2.getD i (0, 0)).1 + +/-- Normalize the supplied incidence route to begin at its clause endpoint. -/ +def sourceRoute (G : Graph) (d : Graph.OrthogonalDrawing) (i : ℕ) : Route := + if (d.2.getD i []).head? = some (sourceStart G d i) then d.2.getD i [] + else (d.2.getD i []).reverse + +/-- Declared vertices with equal positions are equal in a valid drawing. -/ +theorem drawingPosition_injOn {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {v w : ℕ} (hv : v ∈ G.1) (hw : w ∈ G.1) + (he : Graph.drawingPosition G d v = Graph.drawingPosition G d w) : v = w := by + have hi := List.idxOf_lt_length_of_mem hv + have hj := List.idxOf_lt_length_of_mem hw + have hi' : G.1.idxOf v < d.1.length := by rw [hd.vertexCount]; exact hi + have hj' : G.1.idxOf w < d.1.length := by rw [hd.vertexCount]; exact hj + simp only [Graph.drawingPosition, List.getD_eq_getElem _ _ hi', + List.getD_eq_getElem _ _ hj'] at he + have he' := hd.positionsNodup.getElem_inj_iff.mp he + exact (List.idxOf_inj hv).mp he' + +/-- Normalization preserves the complete continuous support of each supplied route. -/ +theorem support_sourceRoute (G : Graph) (d : Graph.OrthogonalDrawing) (i : ℕ) : + support (sourceRoute G d i) = support (d.2.getD i []) := by + unfold sourceRoute + split + · rfl + · exact support_reverse _ + +/-- Normalization preserves the number of route points. -/ +theorem length_sourceRoute (G : Graph) (d : Graph.OrthogonalDrawing) (i : ℕ) : + (sourceRoute G d i).length = (d.2.getD i []).length := by + unfold sourceRoute + split <;> simp + +/-- Normalization preserves the number of segments. -/ +theorem consecutivePairs_length_sourceRoute (G : Graph) (d : Graph.OrthogonalDrawing) + (i : ℕ) : + (sourceRoute G d i).consecutivePairs.length = (d.2.getD i []).consecutivePairs.length := by + unfold sourceRoute + split + · rfl + · exact consecutivePairs_length_reverse _ + +/-- Normalization preserves the total Manhattan wire cost. -/ +theorem wireLength_sourceRoute (G : Graph) (d : Graph.OrthogonalDrawing) (i : ℕ) : + wireLength (sourceRoute G d i) = wireLength (d.2.getD i []) := by + unfold sourceRoute + split + · rfl + · exact wireLength_reverse _ + +/-- The normalized route begins at the clause and ends at the variable. -/ +theorem sourceRoute_endpoints {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i : ℕ} (hi : i < G.2.length) : + (sourceRoute G d i).head? = some (sourceStart G d i) ∧ + (sourceRoute G d i).getLast? = some (sourceFinish G d i) := by + have he := hd.endpoints i hi + dsimp only at he + have hs : sourceStart G d i = Graph.drawingPosition G d G.2[i].2 := by + rw [sourceStart, List.getD_eq_getElem _ _ hi] + have hf : sourceFinish G d i = Graph.drawingPosition G d G.2[i].1 := by + rw [sourceFinish, List.getD_eq_getElem _ _ hi] + rw [← hs, ← hf] at he + unfold sourceRoute + split_ifs with hh + · rcases he with ⟨h₁, h₂⟩ | he + · have heq : sourceFinish G d i = sourceStart G d i := Option.some.inj (h₁.symm.trans hh) + exact ⟨hh, heq ▸ h₂⟩ + · exact he + · rcases he with ⟨h₁, h₂⟩ | ⟨h₁, _⟩ + · simpa only [List.head?_reverse, List.getLast?_reverse] using And.intro h₂ h₁ + · exact False.elim (hh h₁) + +/-- The clause and variable endpoints occupy different points. -/ +theorem sourceStart_ne_finish {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i : ℕ} (hi : i < G.2.length) : + sourceStart G d i ≠ sourceFinish G d i := by + have hm := hd.endpointsDeclared _ (List.getElem_mem hi) + have hn := hd.loopless _ (List.getElem_mem hi) + unfold sourceStart sourceFinish + rw [List.getD_eq_getElem _ _ hi] + exact fun h => hn (drawingPosition_injOn hd hm.1 hm.2 h.symm) + +/-- Every normalized valid incidence has at least two listed points. -/ +theorem sourceRoute_length_ge_two {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i : ℕ} (hi : i < G.2.length) : + 2 ≤ (sourceRoute G d i).length := by + have he := sourceRoute_endpoints hd hi + have hn := sourceStart_ne_finish hd hi + generalize hr : sourceRoute G d i = r at he ⊢ + rcases r with _ | ⟨a, _ | ⟨b, r⟩⟩ + · simp at he + · simp only [List.head?_cons, List.getLast?_singleton, Option.some.injEq] at he + exact False.elim (hn (he.1.symm.trans he.2)) + · simp + +/-- Normalization preserves proper orthogonality and continuous simplicity. -/ +theorem sourceRoute_regular {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i : ℕ} (hi : i < G.2.length) : + IsOrthogonal (sourceRoute G d i) ∧ IsSimple (sourceRoute G d i) := by + unfold sourceRoute + split + · exact ⟨hd.orthogonal i hi, hd.simple i hi⟩ + · exact ⟨(isOrthogonal_reverse_iff _).mpr (hd.orthogonal i hi), + (isSimple_reverse_iff _).mpr (hd.simple i hi)⟩ + +/-- A normalized incidence starts with its actual clause vertex and a proper next point. -/ +theorem sourceRoute_shape {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i : ℕ} (hi : i < G.2.length) : + ∃ b r, sourceRoute G d i = sourceStart G d i :: b :: r := by + have hh := (sourceRoute_endpoints hd hi).1 + obtain ⟨r, hr⟩ := List.head?_eq_some_iff.mp hh + cases r with + | nil => have hlen := sourceRoute_length_ge_two hd hi; simp [hr] at hlen + | cons b r => exact ⟨b, r, hr⟩ + +/-- The cardinal exit direction of a normalized source incidence. -/ +def sourceDirection (G : Graph) (d : Graph.OrthogonalDrawing) (i : ℕ) : Point := + direction (sourceStart G d i) ((sourceRoute G d i).getD 1 (0, 0)) + +/-- Incidences at the same clause vertex leave in different cardinal directions. -/ +theorem sourceDirection_ne {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i j : ℕ} + (hi : i < G.2.length) (hj : j < G.2.length) (hij : i ≠ j) + (hc : G.2[i].2 = G.2[j].2) : sourceDirection G d i ≠ sourceDirection G d j := by + obtain ⟨b, r, hr⟩ := sourceRoute_shape hd hi + obtain ⟨c, s, hs⟩ := sourceRoute_shape hd hj + have he : sourceStart G d i = sourceStart G d j := by + unfold sourceStart + rw [List.getD_eq_getElem _ _ hi, List.getD_eq_getElem _ _ hj, hc] + have ho₁ := (sourceRoute_regular hd hi).1 + have ho₂ := (sourceRoute_regular hd hj).1 + rw [hr] at ho₁ + rw [hs, ← he] at ho₂ + have h₁ := ho₁ (sourceStart G d i, b) (by simp [List.consecutivePairs]) + have h₂ := ho₂ (sourceStart G d i, c) (by simp [List.consecutivePairs]) + have hdir := direction_ne_of_grid_contacts h₁ h₂ (fun p hp hq => ?_) + · simpa [sourceDirection, hr, hs, he, List.getD] using hdir + · have hp' : p ∈ support (d.2.getD i []) := by + rw [← support_sourceRoute, hr, support_cons_cons] + exact Or.inl hp + have hq' : p ∈ support (d.2.getD j []) := by + rw [← support_sourceRoute, hs, ← he, support_cons_cons] + exact Or.inl hq + obtain ⟨v, _, _, _, hv⟩ := hd.contacts i hi j hj hij p hp' hq' + exact ⟨Graph.drawingPosition G d v, hv⟩ + +/-- Scale and trim the clause end of a normalized source incidence. -/ +def exteriorRoute (k radius : ℕ) (G : Graph) (d : Graph.OrthogonalDrawing) (i : ℕ) : Route := + cutStart k radius (sourceRoute G d i) + +/-- The designated contact with the boundary of the local clause box. -/ +def exteriorPort (k radius : ℕ) (G : Graph) (d : Graph.OrthogonalDrawing) (i : ℕ) : Point := + k • sourceStart G d i + radius • sourceDirection G d i + +/-- The trimmed exterior incidence is proper and continuously simple. -/ +theorem exteriorRoute_regular {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i : ℕ} (hi : i < G.2.length) + {k radius : ℕ} (hr : radius < k) : + IsOrthogonal (exteriorRoute k radius G d i) ∧ IsSimple (exteriorRoute k radius G d i) := + ⟨isOrthogonal_cutStart (sourceRoute_regular hd hi).1 hr, + isSimple_cutStart (sourceRoute_regular hd hi).1 (sourceRoute_regular hd hi).2 hr⟩ + +/-- The exterior incidence has its designated box port and the original scaled variable endpoint. -/ +theorem exteriorRoute_endpoints {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i : ℕ} (hi : i < G.2.length) + (k radius : ℕ) : + (exteriorRoute k radius G d i).head? = some (exteriorPort k radius G d i) ∧ + (exteriorRoute k radius G d i).getLast? = some (k • sourceFinish G d i) := by + obtain ⟨b, r, hr⟩ := sourceRoute_shape hd hi + have hlast := (sourceRoute_endpoints hd hi).2 + rw [hr, List.getLast?_cons_cons] at hlast + constructor + · simp [exteriorRoute, exteriorPort, sourceDirection, hr, cutStart, cutPoint, List.getD] + · change (cutStart k radius (sourceRoute G d i)).getLast? = _ + rw [hr] + change (cutPoint k radius (sourceStart G d i) b :: k • b :: dilateRoute k r).getLast? = _ + rw [List.getLast?_cons_cons] + change ((b :: r).map (k • ·)).getLast? = _ + rw [List.getLast?_map, hlast, Option.map_some] + +/-- Cutting removes exactly the radius from the scaled Manhattan cost. -/ +theorem exteriorRoute_wireLength {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i : ℕ} (hi : i < G.2.length) + {k radius : ℕ} (hr : radius < k) : + wireLength (exteriorRoute k radius G d i) + radius = + k * wireLength (d.2.getD i []) := by + rw [← wireLength_sourceRoute G d i] + exact wireLength_cutStart (sourceRoute_regular hd hi).1 (sourceRoute_length_ge_two hd hi) hr + +/-- Exterior incidence routes keep the original number of listed segments. -/ +theorem exteriorRoute_segmentCount (k radius : ℕ) (G : Graph) (d : Graph.OrthogonalDrawing) + (i : ℕ) : + (exteriorRoute k radius G d i).consecutivePairs.length = + (d.2.getD i []).consecutivePairs.length := by + rw [exteriorRoute, consecutivePairs_length_cutStart, consecutivePairs_length_sourceRoute] + +/-- The exterior route meets its own closed clause box only at the designated port. -/ +theorem exteriorRoute_box_contact {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i : ℕ} (hi : i < G.2.length) + {k radius : ℕ} (hr : radius < k) {p : ℝ × ℝ} + (hp : p ∈ support (exteriorRoute k radius G d i)) + (hb : inPointBox (k • sourceStart G d i) radius p) : + p = realPoint (exteriorPort k radius G d i) := by + obtain ⟨b, r, he⟩ := sourceRoute_shape hd hi + have ho := (sourceRoute_regular hd hi).1 + have hs := (sourceRoute_regular hd hi).2 + rw [he] at ho hs + change p ∈ support (cutStart k radius (sourceRoute G d i)) at hp + rw [he] at hp + have hport := cutStart_box_contact ho hs hr hp hb + simpa [exteriorPort, sourceDirection, he, cutPoint, List.getD] using hport + +/-- A nonincident declared vertex has a closed replacement box disjoint from an exterior route. -/ +theorem exteriorRoute_avoids_box {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i : ℕ} (hi : i < G.2.length) + {v : ℕ} (hv : v ∈ G.1) (hv₁ : v ≠ G.2[i].1) (hv₂ : v ≠ G.2[i].2) + {k radius : ℕ} (hr : radius < k) {p : ℝ × ℝ} + (hp : p ∈ support (exteriorRoute k radius G d i)) : + ¬ inPointBox (k • Graph.drawingPosition G d v) radius p := by + intro hb + have hp' := support_cutStart_subset (sourceRoute_regular hd hi).1 hr hp + rw [support_dilateRoute] at hp' + obtain ⟨q, hq, rfl⟩ := hp' + have hnear := near_of_scaled_box hr hb + have hvertex := gridPoint_mem_support_of_near (sourceRoute_regular hd hi).1 hq hnear + rw [support_sourceRoute] at hvertex + exact (hd.avoidsVertices i hi v hv hvertex).elim hv₁ hv₂ + +/-- The exit direction is one of the four cardinal unit vectors. -/ +theorem sourceDirection_unit (G : Graph) (d : Graph.OrthogonalDrawing) (i : ℕ) : + UnitVector (sourceDirection G d i) := direction_unit _ _ + +/-- A positive-radius port is different from the removed source clause center. -/ +theorem exteriorPort_ne_center (k radius : ℕ) (hr : 0 < radius) (G : Graph) + (d : Graph.OrthogonalDrawing) (i : ℕ) : + exteriorPort k radius G d i ≠ k • sourceStart G d i := by + intro he + have hz : radius • sourceDirection G d i = (0 : Point) := + add_left_cancel (he.trans (add_zero _).symm) + change radius • sourceDirection G d i = (0, 0) at hz + have hd := sourceDirection_unit G d i + rcases hd with hd | hd | hd | hd + all_goals + simp only [hd, Prod.smul_mk, Int.nsmul_eq_mul, Prod.mk.injEq, + mul_one, mul_zero, mul_neg_one] at hz + omega + +/-- A trimmed incidence no longer contains the scaled source clause vertex. -/ +theorem exteriorRoute_not_mem_center {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i : ℕ} (hi : i < G.2.length) + {k radius : ℕ} (hr₀ : 0 < radius) (hr : radius < k) : + realPoint (k • sourceStart G d i) ∉ support (exteriorRoute k radius G d i) := by + intro hp + have hb : inPointBox (k • sourceStart G d i) radius + (realPoint (k • sourceStart G d i)) := by + simp only [inPointBox, realPoint] + have hn : (0 : ℝ) ≤ radius := Nat.cast_nonneg _ + constructor + · linarith + constructor + · linarith + constructor <;> linarith + have he := exteriorRoute_box_contact hd hi hr hp hb + exact exteriorPort_ne_center k radius hr₀ G d i (realPoint_injective he.symm) + +private theorem real_dilation_injective {k : ℕ} (hk : k ≠ 0) : + Function.Injective (fun p : ℝ × ℝ => (k : ℝ) • p) := by + rintro ⟨ax, ay⟩ ⟨bx, by'⟩ he + have hk' : (k : ℝ) ≠ 0 := Nat.cast_ne_zero.mpr hk + simp only [Prod.smul_mk, smul_eq_mul, Prod.mk.injEq] at he + exact Prod.ext (mul_left_cancel₀ hk' he.1) (mul_left_cancel₀ hk' he.2) + +/-- Different exterior incidences can meet only at their shared scaled variable endpoint. +The common source clause endpoint has been removed by the positive-radius cut. -/ +theorem exteriorRoute_contacts {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {i j : ℕ} + (hi : i < G.2.length) (hj : j < G.2.length) (hij : i ≠ j) + {k radius : ℕ} (hr₀ : 0 < radius) (hr : radius < k) {p : ℝ × ℝ} + (hp : p ∈ support (exteriorRoute k radius G d i)) + (hq : p ∈ support (exteriorRoute k radius G d j)) : + G.2[i].1 = G.2[j].1 ∧ p = realPoint (k • sourceFinish G d i) := by + have hp' := support_cutStart_subset (sourceRoute_regular hd hi).1 hr hp + have hq' := support_cutStart_subset (sourceRoute_regular hd hj).1 hr hq + rw [support_dilateRoute] at hp' hq' + obtain ⟨q, hq₁, he₁⟩ := hp' + obtain ⟨r, hr₁, he₂⟩ := hq' + have hqr : q = r := real_dilation_injective (by omega : k ≠ 0) (he₁.trans he₂.symm) + subst r + rw [support_sourceRoute] at hq₁ hr₁ + obtain ⟨v, _, hvi, hvj, hv⟩ := hd.contacts i hi j hj hij q hq₁ hr₁ + have hpv : p = realPoint (k • Graph.drawingPosition G d v) := by + rw [← he₁, hv, realPoint_nsmul] + have hvi₂ : v ≠ G.2[i].2 := by + intro he + have hs : sourceStart G d i = Graph.drawingPosition G d v := by + rw [sourceStart, List.getD_eq_getElem _ _ hi, ← he] + exact exteriorRoute_not_mem_center hd hi hr₀ hr (by simpa only [hs, hpv] using hp) + have hvj₂ : v ≠ G.2[j].2 := by + intro he + have hs : sourceStart G d j = Graph.drawingPosition G d v := by + rw [sourceStart, List.getD_eq_getElem _ _ hj, ← he] + exact exteriorRoute_not_mem_center hd hj hr₀ hr (by simpa only [hs, hpv] using hq) + have hvi₁ := hvi.resolve_right hvi₂ + have hvj₁ := hvj.resolve_right hvj₂ + refine ⟨hvi₁.symm.trans hvj₁, ?_⟩ + rw [hpv, sourceFinish, List.getD_eq_getElem _ _ hi, ← hvi₁] + +/-- A different source vertex center lies outside the closed local replacement box. -/ +theorem sourceVertex_not_mem_box {G : Graph} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing G d) {v w : ℕ} (hv : v ∈ G.1) (hw : w ∈ G.1) + (hvw : v ≠ w) {k radius : ℕ} (hr : radius < k) : + ¬ inPointBox (k • Graph.drawingPosition G d v) radius + (realPoint (k • Graph.drawingPosition G d w)) := by + intro hb + rw [realPoint_nsmul] at hb + have he := eq_of_nearGridPoint (near_of_scaled_box hr hb) + exact hvw (drawingPosition_injOn hd hv hw he.symm) + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingValid.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingValid.lean new file mode 100644 index 000000000..fea3d2354 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingValid.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.ExactOne.DrawingAssembly +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingRouteContacts +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingAvoidance +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Nodup + +/-! +# Validity of the actual positive exact-one drawing + +Each actual output occurrence is identified with its finite local incidence. Source ports +splice into the original routes, and all contacts are checked on full real supports. + +## Main results + +* `positiveFormulaDrawing_valid`: all actual output vertices and routes satisfy the geometric + incidence conditions. +* `positiveDrawing_isPlanarOrthogonalDrawing`: the emitted drawing realizes the actual compact + positive exact-one formula. + +## References + +The global replacement and its continuous geometric correctness proof are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- The actual occurrence-indexed compiler has exactly one clause point per output clause. -/ +theorem positiveFormulaDrawing_clauseCount (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) : + (positiveFormulaDrawing φ d).clausePoints.length = + (CNF.PositiveExactOneCompile.rawFormula φ).length := by + simpa only [positiveFormulaDrawing, List.length_map] using + congrArg List.length (positivePlacedClauses_formula φ d) + +/-- Actual output clause positions identify their actual clause indices. -/ +theorem positiveFormulaDrawing_clausePoint_injective {φ : CNF ℕ} + {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) {i j : ℕ} + (hi : i < (CNF.PositiveExactOneCompile.rawFormula φ).length) + (hj : j < (CNF.PositiveExactOneCompile.rawFormula φ).length) + (he : (positiveFormulaDrawing φ d).clausePoints.getD i (0, 0) = + (positiveFormulaDrawing φ d).clausePoints.getD j (0, 0)) : i = j := by + have hlen := positiveFormulaDrawing_clauseCount φ d + rw [List.getD_eq_getElem _ _ (by omega), List.getD_eq_getElem _ _ (by omega)] at he + exact (positiveClausePositions_nodup hd).getElem_inj_iff.mp he + +/-- A clause and its variable identify an actual output occurrence, since output clauses have +distinct variables. -/ +theorem positiveFormulaDrawing_occurrence_injective (φ : CNF ℕ) + {o t : Occurrence (CNF.PositiveExactOneCompile.Variable ℕ)} + (ho : o ∈ Cslib.Logic.PL.occurrences (CNF.PositiveExactOneCompile.rawFormula φ)) + (ht : t ∈ Cslib.Logic.PL.occurrences (CNF.PositiveExactOneCompile.rawFormula φ)) + (hclause : o.1 = t.1) (hvar : o.2.2.1 = t.2.2.1) : o = t := by + rcases o with ⟨i, j, l⟩ + rcases t with ⟨i', j', l'⟩ + change i = i' at hclause + subst i' + obtain ⟨hi, hj, hl⟩ := Cslib.Logic.PL.mem_occurrences.mp ho + obtain ⟨_, hj', hl'⟩ := Cslib.Logic.PL.mem_occurrences.mp ht + have hnd := CNF.PositiveExactOneCompile.nodup_rawFormula φ _ (List.getElem_mem hi) + have he : (((CNF.PositiveExactOneCompile.rawFormula φ)[i]).map Prod.fst)[j]'(by + simpa using hj) = + (((CNF.PositiveExactOneCompile.rawFormula φ)[i]).map Prod.fst)[j']'(by simpa using hj') := by + simpa only [List.getElem_map, ← hl, ← hl'] using hvar + have hji : j = j' := hnd.getElem_inj_iff.mp he + subst j' + have hli : l = l' := hl.trans hl'.symm + subst l' + rfl + +/-- Every actual route begins at its actual output clause and ends at its actual output variable. -/ +theorem positiveFormulaDrawing_endpoints {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + {o : Occurrence (CNF.PositiveExactOneCompile.Variable ℕ)} + (ho : o ∈ Cslib.Logic.PL.occurrences (CNF.PositiveExactOneCompile.rawFormula φ)) : + ((positiveFormulaDrawing φ d).incidenceRoute o).head? = + some ((positiveFormulaDrawing φ d).clausePoints.getD o.1 (0, 0)) ∧ + ((positiveFormulaDrawing φ d).incidenceRoute o).getLast? = + some ((positiveFormulaDrawing φ d).variablePoint o.2.2.1) := by + obtain ⟨i, hi, e, he, hr, hc, hv⟩ := positiveFormulaDrawing_incidence φ d ho + rw [hr, hc] + change _ ∧ _ = some (positiveVariablePoint φ d o.2.2.1) + rw [hv] + exact positiveIncidenceRoute_endpoints hd hi (profilePrefixGeometry_check _) he + +/-- Every actual output route is proper, orthogonal and continuously simple. -/ +theorem positiveFormulaDrawing_regular {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + {o : Occurrence (CNF.PositiveExactOneCompile.Variable ℕ)} + (ho : o ∈ Cslib.Logic.PL.occurrences (CNF.PositiveExactOneCompile.rawFormula φ)) : + IsOrthogonal ((positiveFormulaDrawing φ d).incidenceRoute o) ∧ + IsSimple ((positiveFormulaDrawing φ d).incidenceRoute o) := by + obtain ⟨i, hi, e, he, hr, _, _⟩ := positiveFormulaDrawing_incidence φ d ho + rw [hr] + exact positiveIncidenceRoute_regular hd hi (profilePrefixGeometry_check _) he + +/-- Full continuous contacts of distinct actual occurrences are their actual common endpoints. -/ +theorem positiveFormulaDrawing_contacts {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + {o t : Occurrence (CNF.PositiveExactOneCompile.Variable ℕ)} + (ho : o ∈ Cslib.Logic.PL.occurrences (CNF.PositiveExactOneCompile.rawFormula φ)) + (ht : t ∈ Cslib.Logic.PL.occurrences (CNF.PositiveExactOneCompile.rawFormula φ)) + (hot : o ≠ t) {p : ℝ × ℝ} + (hp : p ∈ support ((positiveFormulaDrawing φ d).incidenceRoute o)) + (hq : p ∈ support ((positiveFormulaDrawing φ d).incidenceRoute t)) : + (o.2.2.1 = t.2.2.1 ∧ p = realPoint ((positiveFormulaDrawing φ d).variablePoint o.2.2.1)) ∨ + (o.1 = t.1 ∧ p = realPoint ((positiveFormulaDrawing φ d).clausePoints.getD o.1 (0, 0))) := by + obtain ⟨i, hi, e, he, hr, hc, hv⟩ := positiveFormulaDrawing_incidence φ d ho + obtain ⟨j, hj, f, hf, hs, hd', hw⟩ := positiveFormulaDrawing_incidence φ d ht + have hne : (i, e) ≠ (j, f) := by + intro hij + have hij' : i = j := congrArg Prod.fst hij + have hef : e = f := congrArg Prod.snd hij + subst j + subst f + have hci := positiveFormulaDrawing_clausePoint_injective hd + (Cslib.Logic.PL.mem_occurrences.mp ho).choose + (Cslib.Logic.PL.mem_occurrences.mp ht).choose (hc.trans hd'.symm) + exact hot (positiveFormulaDrawing_occurrence_injective φ ho ht hci (hv.trans hw.symm)) + rw [hr] at hp + rw [hs] at hq + rcases positiveIncidenceRoute_contacts hd hi hj (fun _ _ => profilePrefixGeometry_check _) + he hf hne hp hq with h | h + · obtain ⟨hij, hef, hp⟩ := h + subst j + have hpoints : (positiveFormulaDrawing φ d).clausePoints.getD o.1 (0, 0) = + (positiveFormulaDrawing φ d).clausePoints.getD t.1 (0, 0) := by + rw [hc, hd', hef] + exact Or.inr ⟨positiveFormulaDrawing_clausePoint_injective hd + (Cslib.Logic.PL.mem_occurrences.mp ho).choose + (Cslib.Logic.PL.mem_occurrences.mp ht).choose hpoints, hc ▸ hp⟩ + · change profileVariableMap _ _ _ _ = profileVariableMap _ _ _ _ ∧ _ at h + refine Or.inl ⟨hv.trans (h.1.trans hw.symm), ?_⟩ + change p = realPoint (positiveVariablePoint φ d o.2.2.1) + rw [hv] + exact h.2 + +/-- All actual output positions and full real route supports satisfy the incidence drawing laws. -/ +theorem positiveFormulaDrawing_valid {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) : + (positiveFormulaDrawing φ d).Valid := by + have hc := positiveFormulaDrawing_clauseCount φ d + have hnd : (positiveFormulaDrawing φ d).positions.Nodup := by + change ((positiveFormulaDrawing φ d).drawing).1.Nodup + rw [positiveFormulaDrawing_drawing] + exact positiveDrawing_positions_nodup hd + have he := fun o ho => positiveFormulaDrawing_endpoints hd (o := o) ho + have hcontacts := fun o ho t ht hot p hp hq => + positiveFormulaDrawing_contacts hd (o := o) (t := t) ho ht hot (p := p) hp hq + refine ⟨hc, hnd, he, (fun o ho => (positiveFormulaDrawing_regular hd ho).1), + (fun o ho => (positiveFormulaDrawing_regular hd ho).2), ?_, ?_, hcontacts⟩ + · exact FormulaDrawing.avoidsVariables_of_contacts hc hnd (fun o ho => (he o ho).2) hcontacts + · apply FormulaDrawing.avoidsClauses_of_contacts hc hnd ?_ (fun o ho => (he o ho).1) hcontacts + intro c hmem hnil + have hlen := CNF.PositiveExactOneCompile.width_rawFormula φ c hmem + rw [hnil] at hlen + contradiction + +/-- The emitted drawing realizes the incidence graph of the actual compact positive formula. -/ +theorem positiveDrawing_isPlanarOrthogonalDrawing {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) : + Graph.IsPlanarOrthogonalDrawing + (SAT.incidenceGraph (CNF.PositiveExactOneCompile.formula φ)) (positiveDrawing φ d) := by + rw [← positiveFormulaDrawing_drawing φ d] + exact FormulaDrawing.isPlanarOrthogonalDrawing_compact _ (positiveFormulaDrawing_valid hd) + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingVertexBoxes.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingVertexBoxes.lean new file mode 100644 index 000000000..3e5b98e6c --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingVertexBoxes.lean @@ -0,0 +1,211 @@ +/- +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.ExactOne.DrawingVertexGeometry +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesPrefix +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Nodup + +/-! +# Separation of actual vertices in different clause boxes + +The dilation separates the full closed boxes around distinct source vertices. Inside one box, +the certified profile and its common injective affine placement distinguish every actual +auxiliary and clause vertex. Inherited source-variable vertices stay at their shared centers. + +## Main results + +* `positiveVariablePoint_injective`: occurring output variables occupy distinct actual points. +* `positiveVariablePositions_nodup`: the actual variable-position prefix has no repetitions. + +## References + +These global placement and injectivity arguments are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- The common closed coordinate bounds of a local collared template. -/ +def InPositiveLocalBox (p : Point) : Prop := + 0 ≤ p.1 ∧ p.1 ≤ 540000 ∧ 0 ≤ p.2 ∧ p.2 ≤ 540000 + +/-- Affine placement sends every point of the inner square strictly inside the collar box. -/ +theorem collarPoint_in_positiveBox (reflected : Bool) {p : Point} + (hp : 0 ≤ p.1 ∧ p.1 ≤ 180000 ∧ 0 ≤ p.2 ∧ p.2 ≤ 180000) : + InPositiveLocalBox (collarPoint reflected p) := by + have hb := collarPoint_in_innerBox reflected hp + norm_num [inPointBox, realPoint] at hb + have hb' : (180000 : ℤ) ≤ (collarPoint reflected p).1 ∧ + (collarPoint reflected p).1 ≤ 360000 ∧ + 180000 ≤ (collarPoint reflected p).2 ∧ (collarPoint reflected p).2 ≤ 360000 := by + exact_mod_cast hb + unfold InPositiveLocalBox + omega + +/-- Every private actual variable lies in its own fixed collared box. -/ +theorem clauseGeometry_auxiliary_bounds (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) {v : CNF.PositiveExactOneCompile.Variable ℕ} + (hv : v ∈ profileVariables (clauseProfile c)) (ha : ∀ n, v ≠ Sum.inl (Sum.inl n)) : + InPositiveLocalBox ((clauseGeometry φ d i c).variablePosition + ((profileVariables (clauseProfile c)).idxOf v)) := by + rw [clauseGeometry_auxiliary_point φ d i c hv ha] + apply collarPoint_in_positiveBox + exact profile_prefix_variable_bounds (clauseProfile c) + ⟨_, profileVariableIndex_lt _ hv⟩ + +/-- Equal placed points in closed local boxes determine the same integer source center. -/ +theorem positiveBox_owner_injective {a b q r : Point} (hq : InPositiveLocalBox q) + (hr : InPositiveLocalBox r) (he : positiveScale • a + q = positiveScale • b + r) : a = b := by + rcases a with ⟨ax, ay⟩ + rcases b with ⟨bx, by'⟩ + rcases q with ⟨qx, qy⟩ + rcases r with ⟨rx, ry⟩ + simp only [positiveScale, Prod.smul_mk, Int.nsmul_eq_mul, + Prod.mk_add_mk, Prod.mk.injEq] at he + simp only [InPositiveLocalBox] at hq hr + apply Prod.ext <;> omega + +/-- A source clause's incidence-graph vertex is declared. -/ +theorem sourceClause_declared (φ : CNF ℕ) {i : ℕ} (hi : i < φ.length) : + φ.distinctVars.length + i ∈ (SAT.incidenceGraph φ).1 := by + simp only [SAT.incidenceGraph, List.mem_range] + omega + +/-- A used source variable's incidence-graph vertex is declared. -/ +theorem sourceVariable_declared (φ : CNF ℕ) {v : ℕ} (hv : v ∈ φ.vars) : + φ.distinctVars.idxOf v ∈ (SAT.incidenceGraph φ).1 := by + have hi := List.idxOf_lt_length_of_mem (CNF.mem_distinctVars.mpr hv) + simp only [SAT.incidenceGraph, List.mem_range] + omega + +/-- Two private profile variables in one actual clause box have different points. -/ +theorem clauseGeometry_auxiliary_injective (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) {v w : CNF.PositiveExactOneCompile.Variable ℕ} + (hv : v ∈ profileVariables (clauseProfile c)) (hw : w ∈ profileVariables (clauseProfile c)) + (hva : ∀ n, v ≠ Sum.inl (Sum.inl n)) (hwa : ∀ n, w ≠ Sum.inl (Sum.inl n)) + (he : (clauseGeometry φ d i c).variablePosition ((profileVariables (clauseProfile c)).idxOf v) = + (clauseGeometry φ d i c).variablePosition ((profileVariables (clauseProfile c)).idxOf w)) : + v = w := by + rw [clauseGeometry_auxiliary_point φ d i c hv hva, + clauseGeometry_auxiliary_point φ d i c hw hwa] at he + have he' := collarPoint_injective _ he + have hidx := prefix_variablePoint_injective (profilePrefixGeometry_check (clauseProfile c)) + (profileVariableIndex_lt _ hv) (profileVariableIndex_lt _ hw) he' + exact (List.idxOf_inj hv).mp hidx + +/-- Equal points of two fresh-variable table entries have the same actual variable key. -/ +theorem positiveAuxiliaryPositions_point_injective {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + {v w : CNF.PositiveExactOneCompile.Variable ℕ} {p q : Point} + (hv : (v, p) ∈ positiveAuxiliaryPositions φ d) + (hw : (w, q) ∈ positiveAuxiliaryPositions φ d) (he : p = q) : v = w := by + obtain ⟨c, i, hci, a, ha, hauxa, hva, hpa⟩ := positiveAuxiliaryPositions_origin φ d hv + obtain ⟨c', j, hcj, b, hb, hauxb, hwb, hqb⟩ := positiveAuxiliaryPositions_origin φ d hw + obtain ⟨hi, hci'⟩ := List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hci) + obtain ⟨hj, hcj'⟩ := List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hcj) + have hplaced := hpa.symm.trans (he.trans hqb) + have hcenter := positiveBox_owner_injective (clauseGeometry_auxiliary_bounds φ d i c ha hauxa) + (clauseGeometry_auxiliary_bounds φ d j c' hb hauxb) hplaced + have hlabels := drawingPosition_injOn hd (sourceClause_declared φ hi) + (sourceClause_declared φ hj) hcenter + have hij : i = j := Nat.add_left_cancel hlabels + subst j + have hcc : c = c' := hci'.symm.trans hcj' + subst c' + have hlocal := add_left_cancel hplaced + have hab := clauseGeometry_auxiliary_injective φ d i c ha hb hauxa hauxb hlocal + rw [hva, hwb, hab] + +/-- A shared source-variable point cannot coincide with any private clause-box point. -/ +theorem positiveSourceAuxiliary_positions_disjoint {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + {v w : CNF.PositiveExactOneCompile.Variable ℕ} {p q : Point} + (hv : (v, p) ∈ positiveSourcePositions φ d) + (hw : (w, q) ∈ positiveAuxiliaryPositions φ d) : p ≠ q := by + obtain ⟨n, hn, hv⟩ := List.mem_map.mp hv + obtain ⟨c, i, hci, a, ha, hauxa, _, hqa⟩ := positiveAuxiliaryPositions_origin φ d hw + have hp : p = positiveScale • + Graph.drawingPosition (SAT.incidenceGraph φ) d (φ.distinctVars.idxOf n) + + positiveTranslation := (congrArg Prod.snd hv).symm + have hnm : n ∈ φ.vars := CNF.mem_distinctVars.mp hn + obtain ⟨hi, _⟩ := List.getElem?_eq_some_iff.mp (List.mem_zipIdx_iff_getElem?.mp hci) + intro he + have hbox : InPositiveLocalBox positiveTranslation := by + norm_num [InPositiveLocalBox, positiveTranslation, positiveRadius] + have hcenters := positiveBox_owner_injective hbox + (clauseGeometry_auxiliary_bounds φ d i c ha hauxa) (hp.symm.trans (he.trans hqa)) + have hlabels := drawingPosition_injOn hd (sourceVariable_declared φ hnm) + (sourceClause_declared φ hi) hcenters + have hlt := List.idxOf_lt_length_of_mem hn + omega + +/-- Shared source-position table entries are injective in their actual source-variable names. -/ +theorem positiveSourcePositions_point_injective {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + {v w : CNF.PositiveExactOneCompile.Variable ℕ} {p q : Point} + (hv : (v, p) ∈ positiveSourcePositions φ d) + (hw : (w, q) ∈ positiveSourcePositions φ d) (he : p = q) : v = w := by + obtain ⟨n, hn, hv⟩ := List.mem_map.mp hv + obtain ⟨m, hm, hw⟩ := List.mem_map.mp hw + have hnm : n ∈ φ.vars := CNF.mem_distinctVars.mp hn + have hmm : m ∈ φ.vars := CNF.mem_distinctVars.mp hm + have hp := congrArg Prod.snd hv + have hq := congrArg Prod.snd hw + have hcenters := nsmul_point_injective (by decide : positiveScale ≠ 0) + (add_right_cancel (hp.trans (he.trans hq.symm))) + have hlabels := drawingPosition_injOn hd (sourceVariable_declared φ hnm) + (sourceVariable_declared φ hmm) hcenters + have hnames := (List.idxOf_inj hn).mp hlabels + have hv' := congrArg Prod.fst hv + have hw' := congrArg Prod.fst hw + change Sum.inl (Sum.inl n) = v at hv' + change Sum.inl (Sum.inl m) = w at hw' + rw [← hv', ← hw', hnames] + +/-- The actual position-table map is injective on all its entries. -/ +theorem positivePositionTable_point_injective {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + {v w : CNF.PositiveExactOneCompile.Variable ℕ} {p q : Point} + (hv : (v, p) ∈ positivePositionTable φ d) (hw : (w, q) ∈ positivePositionTable φ d) + (he : p = q) : v = w := by + rcases List.mem_append.mp hv with hv | hv <;> rcases List.mem_append.mp hw with hw | hw + · exact positiveSourcePositions_point_injective hd hv hw he + · exact False.elim (positiveSourceAuxiliary_positions_disjoint hd hv hw he) + · exact False.elim (positiveSourceAuxiliary_positions_disjoint hd hw hv he.symm) + · exact positiveAuxiliaryPositions_point_injective hd hv hw he + +/-- Distinct occurring output variables occupy distinct actual positions. -/ +theorem positiveVariablePoint_injective {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) + {v w : CNF.PositiveExactOneCompile.Variable ℕ} + (hv : v ∈ (CNF.PositiveExactOneCompile.rawFormula φ).vars) + (hw : w ∈ (CNF.PositiveExactOneCompile.rawFormula φ).vars) + (he : positiveVariablePoint φ d v = positiveVariablePoint φ d w) : v = w := by + obtain ⟨p, hp⟩ := positivePositionTable_covers φ d hv + obtain ⟨q, hq⟩ := positivePositionTable_covers φ d hw + have hkeys := positivePositionTable_keys_nodup φ d + rw [positiveVariablePoint_of_mem φ d hkeys hp, positiveVariablePoint_of_mem φ d hkeys hq] at he + exact positivePositionTable_point_injective hd hp hq he + +/-- The actual variable-position prefix of the emitted drawing has no repetitions. -/ +theorem positiveVariablePositions_nodup {φ : CNF ℕ} {d : Graph.OrthogonalDrawing} + (hd : Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d) : + ((CNF.PositiveExactOneCompile.rawFormula φ).distinctVars.map + (positiveVariablePoint φ d)).Nodup := by + apply List.Nodup.map_on + · intro v hv w hw he + exact positiveVariablePoint_injective hd (CNF.mem_distinctVars.mp hv) + (CNF.mem_distinctVars.mp hw) he + · exact CNF.nodup_distinctVars _ + +end Cslib.Complexity.SAT.ExactOne diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingVertexGeometry.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingVertexGeometry.lean new file mode 100644 index 000000000..b016e9419 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingVertexGeometry.lean @@ -0,0 +1,109 @@ +/- +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.ExactOne.DrawingPositions +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.DrawingEndpoints +public import Complexity.Computability.Complexity.Problems.SAT.ExactOne.GeometryProfilesCollar +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Nodup + +/-! +# Actual vertex geometry inside clause boxes + +Private variables and clauses receive the common injective affine placement of their inner +profile. Source placeholders instead occupy outer boundary ports and are omitted from the +actual global vertex list. + +## Main results + +* `profileVariableIndex_lt`, `profileAuxiliaryIndex_not_terminal`: actual local index scopes. +* `clauseGeometry_auxiliary_point`: affine placement of every private variable. +* `prefix_variablePoint_injective`, `prefix_clausePoint_injective`, + `prefix_variablePoint_ne_clausePoint`: distinct inner vertices remain distinct. + +## References + +These finite support and geometric indexing arguments are original. +-/ + +@[expose] public section + +namespace Cslib.Complexity.SAT.ExactOne + +open Cslib.Logic.PL Orthogonal + +/-- The logical first-occurrence index is a valid geometric variable index. -/ +theorem profileVariableIndex_lt (p : ClauseProfile) + {v : CNF.PositiveExactOneCompile.Variable ℕ} (hv : v ∈ profileVariables p) : + (profileVariables p).idxOf v < (profilePrefixGeometry p).variablePositions.length := by + have hlen : (profilePrefixGeometry p).variablePositions.length = (profileVariables p).length := by + simpa only [profilePrefixGeometry, LocalGeometry.toPrefix, List.length_map, + List.length_zipIdx] using profileGeometry_variable_length p + rw [hlen] + exact List.idxOf_lt_length_of_mem hv + +/-- A private logical variable cannot be one of the exposed source placeholders. -/ +theorem profileAuxiliaryIndex_not_terminal (p : ClauseProfile) + {v : CNF.PositiveExactOneCompile.Variable ℕ} (hv : v ∈ profileVariables p) + (ha : ∀ n, v ≠ Sum.inl (Sum.inl n)) : + (profileVariables p).idxOf v ∉ (profilePrefixGeometry p).terminalVariables := by + intro ht + change (profileVariables p).idxOf v ∈ (profileGeometry p).terminalVariables at ht + have hm : v ∈ (profileGeometry p).terminalVariables.map + (fun i => (profileVariables p).getD i (.inl (.inl 0))) := by + refine List.mem_map.mpr ⟨(profileVariables p).idxOf v, ht, ?_⟩ + rw [List.getD_eq_getElem _ _ (List.idxOf_lt_length_of_mem hv), List.getElem_idxOf] + rw [profileGeometry_terminals] at hm + obtain ⟨n, _, he⟩ := List.mem_map.mp hm + exact ha n he.symm + +/-- Private variables receive the common affine placement of their actual inner points. -/ +theorem clauseGeometry_auxiliary_point (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) + (i : ℕ) (c : Clause ℕ) {v : CNF.PositiveExactOneCompile.Variable ℕ} + (hv : v ∈ profileVariables (clauseProfile c)) (ha : ∀ n, v ≠ Sum.inl (Sum.inl n)) : + (clauseGeometry φ d i c).variablePosition ((profileVariables (clauseProfile c)).idxOf v) = + collarPoint (collarReflected (clausePorts φ d i)) + ((profilePrefixGeometry (clauseProfile c)).variablePosition + ((profileVariables (clauseProfile c)).idxOf v)) := by + rw [clauseGeometry, collared_variablePoint _ _ (profileVariableIndex_lt _ hv), + ite_eq_right (profileAuxiliaryIndex_not_terminal _ hv ha)] + +/-- The coordinate lookup of an actual inner variable is injective. -/ +theorem prefix_variablePoint_injective {g : LocalGeometry} (h : LocalGeometry.PrefixCheck g) + {i j : ℕ} (hi : i < g.variablePositions.length) (hj : j < g.variablePositions.length) + (he : g.variablePosition i = g.variablePosition j) : i = j := by + have hn := (List.nodup_append.mp h.1).1 + simp only [LocalGeometry.variablePosition, List.getD_eq_getElem _ _ hi, + List.getD_eq_getElem _ _ hj] at he + exact hn.getElem_inj_iff.mp he + +/-- The coordinate lookup of an actual inner clause is injective. -/ +theorem prefix_clausePoint_injective {g : LocalGeometry} (h : LocalGeometry.PrefixCheck g) + {i j : ℕ} (hi : i < g.clausePositions.length) (hj : j < g.clausePositions.length) + (he : g.clausePosition i = g.clausePosition j) : i = j := by + have hn := (List.nodup_append.mp h.1).2.1 + simp only [LocalGeometry.clausePosition, List.getD_eq_getElem _ _ hi, + List.getD_eq_getElem _ _ hj] at he + exact hn.getElem_inj_iff.mp he + +/-- An actual inner variable and clause cannot occupy the same point. -/ +theorem prefix_variablePoint_ne_clausePoint {g : LocalGeometry} + (h : LocalGeometry.PrefixCheck g) {i j : ℕ} + (hi : i < g.variablePositions.length) (hj : j < g.clausePositions.length) : + g.variablePosition i ≠ g.clausePosition j := by + have hn := (List.nodup_append.mp h.1).2.2 + have hvi : g.variablePosition i ∈ g.variablePositions := by + simp only [LocalGeometry.variablePosition, List.getD_eq_getElem _ _ hi] + exact List.getElem_mem hi + have hcj : g.clausePosition j ∈ g.clausePositions := by + simp only [LocalGeometry.clausePosition, List.getD_eq_getElem _ _ hj] + exact List.getElem_mem hj + exact hn _ hvi _ hcj + +end Cslib.Complexity.SAT.ExactOne diff --git a/ComplexityTests/PlanarPositiveOneInThreeSAT.lean b/ComplexityTests/PlanarPositiveOneInThreeSAT.lean new file mode 100644 index 000000000..4a076eeef --- /dev/null +++ b/ComplexityTests/PlanarPositiveOneInThreeSAT.lean @@ -0,0 +1,60 @@ +/- +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.PlanarPositiveOneInThreeSAT + +/-! Nonempty, signed and rejected source instances of the actual planar positive compiler. -/ + +namespace ComplexityTests.PlanarPositiveOneInThreeSAT + +open Cslib.Logic.PL Cslib.Complexity + +private def mixedSource : CNF ℕ := + [[(0, true), (1, false)], [(0, false), (2, true)]] + +/-- Both source polarities are compiled away, while all four models are retained. -/ +example : (toPlanarPositiveOneInExactThreeSATThree mixedSource).oneModelCount = 4 := by + rw [oneModelCount_toPlanarPositiveOneInExactThreeSATThree _ (by decide)] + decide + +/-- The nonempty output has the actual emitted full-segment drawing. -/ +example : Graph.IsPlanarOrthogonalDrawing + (SAT.incidenceGraph (toPlanarPositiveOneInExactThreeSATThree mixedSource)) + (toDrawnPlanarPositiveOneInExactThreeSATThree mixedSource).2 := + isPlanarOrthogonalDrawing_toDrawnPlanarPositiveOneInExactThreeSATThree mixedSource + +example : toPlanarPositiveOneInExactThreeSATThree mixedSource ∈ + PlanarPositiveOneInExactThreeSATThree := by + apply (mem_PlanarPositiveOneInExactThreeSATThree_toPlanar_iff mixedSource).mpr + exact ⟨by decide, fun _ => true, by decide⟩ + +/-- Empty source formulas and empty source clauses retain their different counting meanings. -/ +example : (toPlanarPositiveOneInExactThreeSATThree ([] : CNF ℕ)).oneModelCount = 1 := by + rw [oneModelCount_toPlanarPositiveOneInExactThreeSATThree _ (by simp)] + decide + +example : (toPlanarPositiveOneInExactThreeSATThree ([[]] : CNF ℕ)).oneModelCount = 0 := by + rw [oneModelCount_toPlanarPositiveOneInExactThreeSATThree _ (by decide)] + decide + +/-- Repeated source variables do not create repeated variables in an output clause. -/ +example : + (toPlanarPositiveOneInExactThreeSATThree [[(0, true), (0, false)]]).oneModelCount = 2 := by + rw [oneModelCount_toPlanarPositiveOneInExactThreeSATThree _ (by decide)] + decide + +example : ∀ c ∈ toPlanarPositiveOneInExactThreeSATThree [[(0, true), (0, false)]], + c.length = 3 ∧ (c.map Prod.fst).Nodup := + exactClauses_toPlanarPositiveOneInExactThreeSATThree _ + +/-- The total compiler rejects width-four source syntax even when its ordinary formula is true. -/ +example : SharpPositiveOneInExactThreeSATThree + (toPlanarPositiveOneInExactThreeSATThree + [[(0, true), (0, false), (0, true), (0, false)]]) = 0 := by + rw [sharpPositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree] + decide + +end ComplexityTests.PlanarPositiveOneInThreeSAT diff --git a/ROADMAP.md b/ROADMAP.md index fbf1cec3a..66dfaf4fd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -188,8 +188,13 @@ 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. +- The Another Solution Problem (ASP) framework: certify polynomial-time forward and inverse + witness maps. The parsimonious reductions to positive exact-one SAT and its planar + three-occurrence variants already preserve counts over occurring variables; the new local + replacements have constant-factor growth and preserve the earlier linear grid sides and + quadratic planar costs. See the [reduction catalogue](guide/ComplexityGuide/NPCompleteness.lean) + for the completed NP-completeness and NP-hardness endpoints. Exact count preservation alone + does not supply the ASP runtime certificates. - 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. diff --git a/guide/ComplexityGuide/NPCompleteness.lean b/guide/ComplexityGuide/NPCompleteness.lean index 447e7f4b9..ece62fa12 100644 --- a/guide/ComplexityGuide/NPCompleteness.lean +++ b/guide/ComplexityGuide/NPCompleteness.lean @@ -14,6 +14,7 @@ import Complexity.Computability.Complexity.Problems.Hamiltonian.Undirected import Complexity.Computability.Complexity.Problems.IntegerProgramming import Complexity.Computability.Complexity.Problems.Knapsack import Complexity.Computability.Complexity.Problems.Partition +import Complexity.Computability.Complexity.Problems.PlanarPositiveOneInThreeSAT import Complexity.Computability.Complexity.Problems.PlanarThreeSATThree import Complexity.Computability.Complexity.Problems.SubsetSum import Complexity.Computability.Complexity.Problems.TSP @@ -256,6 +257,37 @@ Here is the tree, with the reduction for each edge and the completeness theorem For original size `S = clauses + literal occurrences + 1`, its grid sides are at most `13440*S`; formula size, route count, segment count and Manhattan wire length are quadratic in `S` ({name}`toDrawnPlanarThreeSAT_fits`, {name}`toDrawnPlanarThreeSAT_costs`). +* Positive 1-in-3SAT and positive 1-in-E3SAT are NP-complete, with or without the restriction + that each variable occurs at most three times ({name}`PositiveOneInThreeSAT_npComplete`, + {name}`PositiveOneInExactThreeSAT_npComplete`, {name}`PositiveOneInThreeSATThree_npComplete`, + {name}`PositiveOneInExactThreeSATThree_npComplete`). Positive means no negated literals; + exactly one literal occurrence must be true in each clause. E3 requires exactly three + distinct variables per clause. The same compiler {name}`toPositiveOneInExactThreeSATThree` + produces this strongest syntax and, on valid 3SAT inputs, preserves counts on occurring + variables ({name}`oneModelCount_toPositiveOneInExactThreeSATThree`). For `m` source clauses + and `ℓ` literal occurrences, it produces at most `169*(m + ℓ)` clauses and `507*(m + ℓ)` + occurrences ({name}`size_toPositiveOneInExactThreeSATThree`). +* Planar positive 1-in-E3SAT-3 is NP-hard both with an existential drawing + ({name}`PlanarPositiveOneInExactThreeSATThree_npHard`) and with its actual drawing supplied + ({name}`DrawnPlanarPositiveOneInExactThreeSATThree_npHard`). The width-at-most-three planar + variant is also NP-hard ({name}`PlanarPositiveOneInThreeSATThree_npHard`). These endpoints + assert NP-hardness; the NP membership proofs above concern the nonplanar languages. + The local positive replacement scales supplied grid sides from `W,H` to at most + `1080000*(W + 1),1080000*(H + 1)` ({name}`SAT.ExactOne.positiveDrawing_fits`), with + constant-factor formula and drawing costs ({name}`SAT.ExactOne.positiveDrawing_costs`). + Composed with ordinary 3SAT planarization, grid sides remain linear and formula size, + vertices, routes, segments and Manhattan wire length remain quadratic in + `S = clauses + literal occurrences + 1` + ({name}`toDrawnPlanarPositiveOneInExactThreeSATThree_fits`, + {name}`toDrawnPlanarPositiveOneInExactThreeSATThree_costs`). + The full compiler {name}`toDrawnPlanarPositiveOneInExactThreeSATThree` computes the actual + formula, binary coordinates and route lists in native polynomial time + ({name}`computableInPolyTime_toDrawnPlanarPositiveOneInExactThreeSATThree`). On valid 3SAT + inputs it preserves the exact model count + ({name}`oneModelCount_toPlanarPositiveOneInExactThreeSATThree`). + The positive gadgets follow + [Hunt et al., Theorem 3.8](https://arxiv.org/pdf/cs/9809017v1), Figures 3.4–3.5; + their satisfying extensions are unique. * Subset sum reduces to partition ({name}`polyTimeReducible_SubsetSum_Partition`, {name}`Partition_npComplete`), knapsack ({name}`polyTimeReducible_SubsetSum_Knapsack`, {name}`Knapsack_npComplete`) and zero-one integer programming From c0befbd63c321e98afc30b42e297dc20bfd99ca3 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Mon, 26 Oct 2026 22:58:36 -0400 Subject: [PATCH 4/5] doc(geometry): correct module summary declaration names Correct three stale Main results references found by the changed-file audit. Definitions, statements and proofs are unchanged. --- .../Complexity/Problems/SAT/ExactOne/GeometryPrefix.lean | 2 +- Complexity/Foundations/Geometry/Orthogonal/Isolation.lean | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryPrefix.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryPrefix.lean index fbd09b417..3fe7c4d20 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryPrefix.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryPrefix.lean @@ -26,7 +26,7 @@ box boundary only at its own variable endpoint. ## Main results -* `LocalGeometry.toPrefix_boundary_contact`: only the variable endpoint can touch the boundary. +* `LocalGeometry.prefix_boundary_contact`: only the variable endpoint can touch the boundary. ## References diff --git a/Complexity/Foundations/Geometry/Orthogonal/Isolation.lean b/Complexity/Foundations/Geometry/Orthogonal/Isolation.lean index 1fb4ffc0c..495fd2360 100644 --- a/Complexity/Foundations/Geometry/Orthogonal/Isolation.lean +++ b/Complexity/Foundations/Geometry/Orthogonal/Isolation.lean @@ -26,7 +26,7 @@ drawing-dependent real radius. ## Main results * `gridPoint_mem_segment_of_near`, `gridPoint_mem_support_of_near`. -* `IsSimple.segment_eq_first_of_head_mem`, `IsSimple.mem_first_segment_of_near_head`. +* `segment_eq_first_of_head_mem`, `mem_first_segment_of_near_head`. ## References From 93331ccbfab845cc064476ca49090ed54588b314 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Thu, 29 Oct 2026 08:56:31 +0000 Subject: [PATCH 5/5] fix(planar-SAT): guard the packaged parsimony targets by planarity Address the review of #1282: the two packaged parsimonious reductions now target SharpPlanarPositiveOneInExactThreeSATThree and SharpDrawnPlanarPositiveOneInExactThreeSATThree, which count only when a planar orthogonal incidence drawing exists or the supplied drawing is one. Adapt DrawingClauseVertices to main's removal of profile_prefix_clause_interior (#1403) via profile_prefix_clause_bounds, and test the guarded count on the mixed source. --- .../Problems/PlanarPositiveOneInThreeSAT.lean | 54 +++++++++++++++---- .../SAT/ExactOne/DrawingClauseVertices.lean | 4 +- .../PlanarPositiveOneInThreeSAT.lean | 13 +++++ 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/Complexity/Computability/Complexity/Problems/PlanarPositiveOneInThreeSAT.lean b/Complexity/Computability/Complexity/Problems/PlanarPositiveOneInThreeSAT.lean index 948283d59..4e622b0d7 100644 --- a/Complexity/Computability/Complexity/Problems/PlanarPositiveOneInThreeSAT.lean +++ b/Complexity/Computability/Complexity/Problems/PlanarPositiveOneInThreeSAT.lean @@ -32,12 +32,18 @@ counted only on occurring variables, and every ordinary source model has a uniqu * `PlanarPositiveOneInExactThreeSATThree`: the existential orthogonal-drawing language. * `DrawnPlanarPositiveOneInExactThreeSATThree`: the supplied-drawing language. +* `SharpPlanarPositiveOneInExactThreeSATThree`, `SharpDrawnPlanarPositiveOneInExactThreeSATThree`: + the positive exact-one model counts guarded by the existence, respectively the validity, of a + planar orthogonal incidence drawing. * `toPlanarPositiveOneInExactThreeSATThree`, `toDrawnPlanarPositiveOneInExactThreeSATThree`: the actual formula and supplied-drawing compilers. ## Main results * `oneModelCount_toPlanarPositiveOneInExactThreeSATThree`: preservation of model counts. +* `parsimoniousReducible_SharpThreeSAT_SharpPlanarPositiveOneInExactThreeSATThree`, + `parsimoniousReducible_SharpThreeSAT_SharpDrawnPlanarPositiveOneInExactThreeSATThree`: native + parsimonious reductions from `#3SAT` to the planarity-guarded counts. * `PlanarPositiveOneInExactThreeSATThree_npHard`, `PlanarPositiveOneInThreeSATThree_npHard`, `DrawnPlanarPositiveOneInExactThreeSATThree_npHard`: NP-hardness of the planar positive variants, including the version with the actual drawing supplied. @@ -75,6 +81,21 @@ def PlanarPositiveOneInThreeSATThree : Set (CNF ℕ) := {φ | φ ∈ PositiveOneInThreeSATThree ∧ ∃ d, Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d} +open Classical in +/-- Count positive exact-three, occurrence-three exact-one models of formulas that have a planar +orthogonal incidence drawing; return zero otherwise. -/ +noncomputable def SharpPlanarPositiveOneInExactThreeSATThree (φ : CNF ℕ) : ℕ := + if ∃ d, Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph φ) d then + SharpPositiveOneInExactThreeSATThree φ else 0 + +open Classical in +/-- Count positive exact-three, occurrence-three exact-one models when the supplied drawing is a +planar orthogonal incidence drawing of the formula; return zero otherwise. -/ +noncomputable def SharpDrawnPlanarPositiveOneInExactThreeSATThree + (x : CNF ℕ × Graph.OrthogonalDrawing) : ℕ := + if Graph.IsPlanarOrthogonalDrawing (SAT.incidenceGraph x.1) x.2 then + SharpPositiveOneInExactThreeSATThree x.1 else 0 + /-- Apply only the constant-factor positive compiler after the existing planarization. -/ def toPlanarPositiveOneInExactThreeSATThree (φ : CNF ℕ) : CNF ℕ := CNF.PositiveExactOneCompile.formula (toPlanarThreeSAT φ) @@ -237,12 +258,19 @@ theorem sharpPositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATT simpa only [SharpThreeSATThree, hsource, ↓reduceIte, SharpSAT] using sharpThreeSATThree_toThreeSATThree φ) -/-- The planar formula compiler is a native parsimonious reduction of guarded counting problems. -/ -theorem parsimoniousReducible_SharpThreeSAT_planarPositiveOneInExactThreeSATThree : - ParsimoniousReducible encCNF encCNF SharpThreeSAT SharpPositiveOneInExactThreeSATThree := +/-- The planar formula compiler is a native parsimonious reduction to the planarity-guarded count: +every output has a planar orthogonal incidence drawing, so the guard never discards a model. -/ +theorem parsimoniousReducible_SharpThreeSAT_SharpPlanarPositiveOneInExactThreeSATThree : + ParsimoniousReducible encCNF encCNF SharpThreeSAT + SharpPlanarPositiveOneInExactThreeSATThree := ⟨toPlanarPositiveOneInExactThreeSATThree, - computableInPolyTime_toPlanarPositiveOneInExactThreeSATThree, - fun φ => (sharpPositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree φ).symm⟩ + computableInPolyTime_toPlanarPositiveOneInExactThreeSATThree, fun φ => by + rw [SharpPlanarPositiveOneInExactThreeSATThree] + split + · exact (sharpPositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree φ).symm + · rename_i h + exact + (h ⟨_, isPlanarOrthogonalDrawing_toDrawnPlanarPositiveOneInExactThreeSATThree φ⟩).elim⟩ /-- The full compiler retains linear grid sides relative to the original ordinary 3SAT input. -/ theorem toDrawnPlanarPositiveOneInExactThreeSATThree_fits (φ : CNF ℕ) : @@ -298,12 +326,18 @@ theorem DrawnPlanarPositiveOneInExactThreeSATThree_npHard : NPHard (encPair encCNF Graph.encOrthogonalDrawing) DrawnPlanarPositiveOneInExactThreeSATThree := ThreeSAT_npHard.trans polyTimeReducible_ThreeSAT_DrawnPlanarPositiveOneInExactThreeSATThree -/-- Supplying the emitted drawing preserves the same exact guarded model counts. -/ -theorem parsimoniousReducible_SharpThreeSAT_drawnPlanarPositiveOneInExactThreeSATThree : +/-- Supplying the emitted drawing gives a native parsimonious reduction to the count guarded by the +validity of the supplied drawing: the emitted drawing is always a planar orthogonal incidence +drawing of the emitted formula. -/ +theorem parsimoniousReducible_SharpThreeSAT_SharpDrawnPlanarPositiveOneInExactThreeSATThree : ParsimoniousReducible encCNF (encPair encCNF Graph.encOrthogonalDrawing) SharpThreeSAT - (fun x => SharpPositiveOneInExactThreeSATThree x.1) := + SharpDrawnPlanarPositiveOneInExactThreeSATThree := ⟨toDrawnPlanarPositiveOneInExactThreeSATThree, - computableInPolyTime_toDrawnPlanarPositiveOneInExactThreeSATThree, - fun φ => (sharpPositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree φ).symm⟩ + computableInPolyTime_toDrawnPlanarPositiveOneInExactThreeSATThree, fun φ => by + rw [SharpDrawnPlanarPositiveOneInExactThreeSATThree] + split + · exact (sharpPositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree φ).symm + · rename_i h + exact (h (isPlanarOrthogonalDrawing_toDrawnPlanarPositiveOneInExactThreeSATThree φ)).elim⟩ end Cslib.Complexity diff --git a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingClauseVertices.lean b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingClauseVertices.lean index 01f826ae2..a1aa0e413 100644 --- a/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingClauseVertices.lean +++ b/Complexity/Computability/Complexity/Problems/SAT/ExactOne/DrawingClauseVertices.lean @@ -69,8 +69,8 @@ theorem clauseGeometry_clause_bounds (φ : CNF ℕ) (d : Graph.OrthogonalDrawing InPositiveLocalBox p := by obtain ⟨q, hq, rfl⟩ := List.mem_map.mp hp apply collarPoint_in_positiveBox - have hb := profile_prefix_clause_interior (clauseProfile c) q hq - exact ⟨hb.1.le, hb.2.1.le, hb.2.2.1.le, hb.2.2.2.le⟩ + obtain ⟨i, rfl⟩ := List.get_of_mem hq + simpa [LocalGeometry.clausePosition] using profile_prefix_clause_bounds (clauseProfile c) i /-- A single actual clause box has distinct clause points. -/ theorem positiveClauseBlock_positions_nodup (φ : CNF ℕ) (d : Graph.OrthogonalDrawing) diff --git a/ComplexityTests/PlanarPositiveOneInThreeSAT.lean b/ComplexityTests/PlanarPositiveOneInThreeSAT.lean index 4a076eeef..74f78775a 100644 --- a/ComplexityTests/PlanarPositiveOneInThreeSAT.lean +++ b/ComplexityTests/PlanarPositiveOneInThreeSAT.lean @@ -26,6 +26,19 @@ example : Graph.IsPlanarOrthogonalDrawing (toDrawnPlanarPositiveOneInExactThreeSATThree mixedSource).2 := isPlanarOrthogonalDrawing_toDrawnPlanarPositiveOneInExactThreeSATThree mixedSource +/-- The emitted drawing passes the planarity guard, so the guarded count keeps all four models. -/ +example : SharpDrawnPlanarPositiveOneInExactThreeSATThree + (toDrawnPlanarPositiveOneInExactThreeSATThree mixedSource) = 4 := by + rw [SharpDrawnPlanarPositiveOneInExactThreeSATThree] + split + · rw [show (toDrawnPlanarPositiveOneInExactThreeSATThree mixedSource).1 = + toPlanarPositiveOneInExactThreeSATThree mixedSource from rfl, + sharpPositiveOneInExactThreeSATThree_toPlanarPositiveOneInExactThreeSATThree] + decide + · rename_i h + exact + (h (isPlanarOrthogonalDrawing_toDrawnPlanarPositiveOneInExactThreeSATThree mixedSource)).elim + example : toPlanarPositiveOneInExactThreeSATThree mixedSource ∈ PlanarPositiveOneInExactThreeSATThree := by apply (mem_PlanarPositiveOneInExactThreeSATThree_toPlanar_iff mixedSource).mpr