diff --git a/Complexity.lean b/Complexity.lean index daa7b1aad..b9a23bf53 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -947,8 +947,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..4e622b0d7 --- /dev/null +++ b/Complexity/Computability/Complexity/Problems/PlanarPositiveOneInThreeSAT.lean @@ -0,0 +1,343 @@ +/- +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. +* `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. +* `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} + +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 φ) + +/-- 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 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 φ => 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 ℕ) : + 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 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 + SharpDrawnPlanarPositiveOneInExactThreeSATThree := + ⟨toDrawnPlanarPositiveOneInExactThreeSATThree, + 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/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..a1aa0e413 --- /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 + 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) + (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..74f78775a --- /dev/null +++ b/ComplexityTests/PlanarPositiveOneInThreeSAT.lean @@ -0,0 +1,73 @@ +/- +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 + +/-- 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 + 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 c3a58d48f..5fc6cb5a6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -215,8 +215,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