2026-06-09 18:36:43 -07:00
|
|
|
|
/-
|
|
|
|
|
|
Port of `Lattice.agda`.
|
|
|
|
|
|
|
|
|
|
|
|
Most of the Agda module is *lifted* into mathlib, since we now work with
|
|
|
|
|
|
propositional equality instead of a setoid:
|
|
|
|
|
|
|
|
|
|
|
|
IsSemilattice A _≈_ _⊔_ ↦ SemilatticeSup α
|
|
|
|
|
|
IsLattice A _≈_ _⊔_ _⊓_ ↦ Lattice α
|
|
|
|
|
|
_≼_ (a ⊔ b ≈ b) ↦ a ≤ b (bridge: `sup_eq_right`)
|
|
|
|
|
|
_≺_ ↦ a < b
|
|
|
|
|
|
Monotonic ↦ Monotone
|
|
|
|
|
|
⊔-assoc/⊔-comm/⊔-idemp ↦ sup_assoc/sup_comm/sup_idem
|
|
|
|
|
|
absorb-⊔-⊓/absorb-⊓-⊔ ↦ sup_inf_self/inf_sup_self
|
|
|
|
|
|
≼-refl/≼-trans/≼-antisym ↦ le_refl/le_trans/le_antisymm
|
|
|
|
|
|
x≼x⊔y ↦ le_sup_left
|
|
|
|
|
|
⊔-Monotonicˡ/ʳ ↦ sup_le_sup_left/sup_le_sup_right
|
|
|
|
|
|
id-Mono/const-Mono ↦ monotone_id/monotone_const
|
|
|
|
|
|
IsDecidable ↦ DecidableEq (kept only where computation needs it)
|
|
|
|
|
|
Chain (Chain.agda) ↦ LTSeries (chains of `<`); concat ↦ RelSeries.smash
|
|
|
|
|
|
ChainMapping.Chain-map ↦ LTSeries.map (Monotone + Injective ⇒ StrictMono)
|
|
|
|
|
|
|
|
|
|
|
|
What remains custom is exactly what mathlib does not have:
|
|
|
|
|
|
* monotonicity of folds over pairwise-related lists (foldr-Mono & friends),
|
|
|
|
|
|
* the fixed-height machinery (Chain.Height ↦ FixedHeight, Bounded),
|
|
|
|
|
|
* the proof that the bottom of the longest chain is a least element (⊥≼).
|
|
|
|
|
|
-/
|
|
|
|
|
|
import Mathlib.Order.Lattice
|
|
|
|
|
|
import Mathlib.Order.RelSeries
|
|
|
|
|
|
|
|
|
|
|
|
namespace Spa
|
|
|
|
|
|
|
|
|
|
|
|
/-! ### Monotonicity helpers (Lattice.agda, `Monotonic₂` and fold lemmas) -/
|
|
|
|
|
|
|
|
|
|
|
|
/-- Agda: `Monotonic₂` (a pair of one-sided monotonicity proofs). -/
|
|
|
|
|
|
def Monotone₂ {α β γ : Type*} [Preorder α] [Preorder β] [Preorder γ]
|
|
|
|
|
|
(f : α → β → γ) : Prop :=
|
|
|
|
|
|
(∀ b, Monotone fun a => f a b) ∧ (∀ a, Monotone (f a))
|
|
|
|
|
|
|
|
|
|
|
|
section Folds
|
|
|
|
|
|
|
|
|
|
|
|
variable {α β : Type*} [Preorder α] [Preorder β]
|
|
|
|
|
|
|
|
|
|
|
|
/-- Agda: `foldr-Mono`. `Pairwise _≼₁_` becomes `List.Forall₂ (· ≤ ·)`. -/
|
|
|
|
|
|
theorem foldr_mono {l₁ l₂ : List α} (f : α → β → β) {b₁ b₂ : β}
|
|
|
|
|
|
(hl : List.Forall₂ (· ≤ ·) l₁ l₂) (hb : b₁ ≤ b₂)
|
|
|
|
|
|
(hf₁ : ∀ b, Monotone fun a => f a b) (hf₂ : ∀ a, Monotone (f a)) :
|
|
|
|
|
|
l₁.foldr f b₁ ≤ l₂.foldr f b₂ := by
|
|
|
|
|
|
induction hl with
|
|
|
|
|
|
| nil => exact hb
|
|
|
|
|
|
| cons hxy _ ih =>
|
|
|
|
|
|
exact le_trans (hf₁ _ hxy) (hf₂ _ ih)
|
|
|
|
|
|
|
|
|
|
|
|
/-- Agda: `foldl-Mono`. -/
|
|
|
|
|
|
theorem foldl_mono {l₁ l₂ : List α} (f : β → α → β) {b₁ b₂ : β}
|
|
|
|
|
|
(hl : List.Forall₂ (· ≤ ·) l₁ l₂) (hb : b₁ ≤ b₂)
|
|
|
|
|
|
(hf₁ : ∀ a, Monotone fun b => f b a) (hf₂ : ∀ b, Monotone (f b)) :
|
|
|
|
|
|
l₁.foldl f b₁ ≤ l₂.foldl f b₂ := by
|
|
|
|
|
|
induction hl generalizing b₁ b₂ with
|
|
|
|
|
|
| nil => exact hb
|
|
|
|
|
|
| cons hxy _ ih =>
|
|
|
|
|
|
exact ih (le_trans (hf₁ _ hb) (hf₂ _ hxy))
|
|
|
|
|
|
|
|
|
|
|
|
omit [Preorder α] in
|
|
|
|
|
|
/-- Agda: `foldr-Mono'` (fixed list, varying accumulator). -/
|
|
|
|
|
|
theorem foldr_mono' (l : List α) (f : α → β → β)
|
|
|
|
|
|
(hf : ∀ a, Monotone (f a)) : Monotone fun b => l.foldr f b := by
|
|
|
|
|
|
intro b₁ b₂ hb
|
|
|
|
|
|
induction l with
|
|
|
|
|
|
| nil => exact hb
|
|
|
|
|
|
| cons x xs ih => exact hf x ih
|
|
|
|
|
|
|
|
|
|
|
|
omit [Preorder α] in
|
|
|
|
|
|
/-- Agda: `foldl-Mono'`. -/
|
|
|
|
|
|
theorem foldl_mono' (l : List α) (f : β → α → β)
|
|
|
|
|
|
(hf : ∀ a, Monotone fun b => f b a) : Monotone fun b => l.foldl f b := by
|
|
|
|
|
|
intro b₁ b₂ hb
|
|
|
|
|
|
induction l generalizing b₁ b₂ with
|
|
|
|
|
|
| nil => exact hb
|
|
|
|
|
|
| cons x xs ih => exact ih (hf x hb)
|
|
|
|
|
|
|
|
|
|
|
|
end Folds
|
|
|
|
|
|
|
|
|
|
|
|
/-! ### Fixed height (Chain.agda `Bounded`/`Height`, Lattice.agda `FixedHeight`) -/
|
|
|
|
|
|
|
|
|
|
|
|
/-- Agda: `Chain.Bounded`. Every `<`-chain has length at most `n`. -/
|
|
|
|
|
|
def BoundedChains (α : Type*) [Preorder α] (n : ℕ) : Prop :=
|
|
|
|
|
|
∀ c : LTSeries α, c.length ≤ n
|
|
|
|
|
|
|
|
|
|
|
|
/-- Agda: `Chain.Height` (with `FixedHeight h = Height h` from Lattice.agda).
|
|
|
|
|
|
A longest chain runs from `⊥` to `⊤` and has length exactly `height`;
|
|
|
|
|
|
no chain is longer. -/
|
|
|
|
|
|
structure FixedHeight (α : Type*) [Preorder α] (height : ℕ) where
|
|
|
|
|
|
bot : α
|
|
|
|
|
|
top : α
|
|
|
|
|
|
longestChain : LTSeries α
|
|
|
|
|
|
head_longestChain : longestChain.head = bot
|
|
|
|
|
|
last_longestChain : longestChain.last = top
|
|
|
|
|
|
length_longestChain : longestChain.length = height
|
|
|
|
|
|
bounded : BoundedChains α height
|
|
|
|
|
|
|
|
|
|
|
|
/-- Agda: `Chain.Bounded-suc-n` (a bounded order admits no chain one longer). -/
|
|
|
|
|
|
theorem BoundedChains.no_longer {α : Type*} [Preorder α] {n : ℕ}
|
|
|
|
|
|
(h : BoundedChains α n) (c : LTSeries α) : c.length ≠ n + 1 :=
|
|
|
|
|
|
fun hc => absurd (h c) (by omega)
|
|
|
|
|
|
|
Lean migration: Phase 4 (IterProd + FiniteMap lattices)
- Spa.Lattice.IterProd: k-fold product, recursive Lattice instance,
fixed height k*hA + hB, bot = build of bottoms
- Spa.Lattice.FiniteMap: spine-pinned assoc lists ({l // l.map fst = ks});
with = the 1100-line Map.agda collapses into positional 'combine'.
Same lemma inventory (membership, locate, updating, GeneralizedUpdate,
valuesAt, Provenance-union, le_of_mem_mem) — Nodup is now an explicit
hypothesis where the Agda Map carried it intrinsically. Fixed height
|ks|*hB still via transport along the IterProd isomorphism, which no
longer needs Unique ks (representation is canonical).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 19:12:39 -07:00
|
|
|
|
/-- Re-index a `FixedHeight` along an equality of heights (used where Agda
|
|
|
|
|
|
just rewrites with arithmetic identities). -/
|
|
|
|
|
|
def FixedHeight.cast {α : Type*} [Preorder α] {m n : ℕ} (h : m = n)
|
|
|
|
|
|
(fh : FixedHeight α m) : FixedHeight α n where
|
|
|
|
|
|
bot := fh.bot
|
|
|
|
|
|
top := fh.top
|
|
|
|
|
|
longestChain := fh.longestChain
|
|
|
|
|
|
head_longestChain := fh.head_longestChain
|
|
|
|
|
|
last_longestChain := fh.last_longestChain
|
|
|
|
|
|
length_longestChain := h ▸ fh.length_longestChain
|
|
|
|
|
|
bounded := h ▸ fh.bounded
|
|
|
|
|
|
|
|
|
|
|
|
@[simp] theorem FixedHeight.cast_bot {α : Type*} [Preorder α] {m n : ℕ}
|
|
|
|
|
|
(h : m = n) (fh : FixedHeight α m) : (fh.cast h).bot = fh.bot := rfl
|
|
|
|
|
|
|
Lean migration: typeclass-based parameter passing, as in the Agda original
The port had flattened Agda's instance arguments ({{flA}}, {{evaluator}},
{{latticeInterpretation}}, {{validEvaluator}}) into explicitly threaded
values (fhL, E, I, hE). Restore them as typeclasses:
- Spa.FiniteHeightLattice: now actually used — Fixedpoint takes the
instance instead of a FixedHeight value; FiniteMap gets the missing
instance (height = ks.length * height B), so varsFixedHeight /
statesFixedHeight / signFixedHeight / constFixedHeight plumbing
disappears (instance bottoms are defeq to the old ones)
- Spa.Analysis.Forward.Evaluation: StmtEvaluator/ExprEvaluator become
classes; the Valid* Props become Prop-classes, as in Agda
- Spa.Analysis.Forward.Adapters: the expr→stmt adapter and its validity
are instances (Agda: the ExprToStmtAdapter instances)
- LatticeInterpretation is a class; sign/const interpretations,
evaluators and validity proofs are instances; use sites read like the
Agda module applications: result SignLattice prog
Proof simplifications (same theorems, proofs factored):
- Spa.Lattice.AboveBelow.monotone₂_of_strict: any ⊥-strict/⊤-dominated
operation on a flat lattice is monotone — replaces the four near-
identical case bashes per analysis (postulates in Agda)
- Spa.Lattice.AboveBelow.interp_sup_of/interp_inf_of: the shared flat-
lattice interpretation case analysis, making interpSign_sup/inf and
interpConst_sup/inf one-liners
lake build green with zero warnings; lake exe spa output verified
byte-identical (diff) to the previous, Agda-verified output.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 23:32:38 -07:00
|
|
|
|
/-- Agda: `IsFiniteHeightLattice` / `FiniteHeightLattice` (bundled). Like the
|
|
|
|
|
|
Agda code (which took `IsFiniteHeightLattice` as an instance argument `⦃·⦄`),
|
|
|
|
|
|
this is a typeclass; downstream modules pick it up by instance resolution
|
|
|
|
|
|
rather than threading a `FixedHeight` value. -/
|
2026-06-09 18:36:43 -07:00
|
|
|
|
class FiniteHeightLattice (α : Type*) [Lattice α] where
|
|
|
|
|
|
height : ℕ
|
|
|
|
|
|
fixedHeight : FixedHeight α height
|
|
|
|
|
|
|
|
|
|
|
|
namespace FixedHeight
|
|
|
|
|
|
|
|
|
|
|
|
variable {α : Type*} [Lattice α] {h : ℕ}
|
|
|
|
|
|
|
|
|
|
|
|
/-- Agda: `Known-⊥`. -/
|
|
|
|
|
|
def KnownBot (fh : FixedHeight α h) : Prop := ∀ a : α, fh.bot ≤ a
|
|
|
|
|
|
|
|
|
|
|
|
/-- Agda: `Known-⊤`. -/
|
|
|
|
|
|
def KnownTop (fh : FixedHeight α h) : Prop := ∀ a : α, a ≤ fh.top
|
|
|
|
|
|
|
|
|
|
|
|
/-- Agda: `⊥≼` — the bottom of the longest chain is a least element.
|
|
|
|
|
|
Same proof: if `⊥ ⊓ a ≠ ⊥` then `⊥ ⊓ a < ⊥` prepends to the longest chain,
|
|
|
|
|
|
contradicting boundedness. (The decidability hypothesis of the Agda proof is
|
|
|
|
|
|
not needed classically.) -/
|
|
|
|
|
|
theorem bot_le (fh : FixedHeight α h) : fh.KnownBot := by
|
|
|
|
|
|
intro a
|
|
|
|
|
|
by_cases heq : fh.bot ⊓ a = fh.bot
|
|
|
|
|
|
· exact inf_eq_left.mp heq
|
|
|
|
|
|
· exfalso
|
|
|
|
|
|
have hlt : fh.bot ⊓ a < fh.bot :=
|
|
|
|
|
|
lt_of_le_of_ne inf_le_left heq
|
|
|
|
|
|
exact fh.bounded.no_longer
|
|
|
|
|
|
(fh.longestChain.cons (fh.bot ⊓ a) (fh.head_longestChain ▸ hlt))
|
|
|
|
|
|
(by simp [RelSeries.cons, fh.length_longestChain])
|
|
|
|
|
|
|
|
|
|
|
|
end FixedHeight
|
|
|
|
|
|
|
Lean migration: typeclass-based parameter passing, as in the Agda original
The port had flattened Agda's instance arguments ({{flA}}, {{evaluator}},
{{latticeInterpretation}}, {{validEvaluator}}) into explicitly threaded
values (fhL, E, I, hE). Restore them as typeclasses:
- Spa.FiniteHeightLattice: now actually used — Fixedpoint takes the
instance instead of a FixedHeight value; FiniteMap gets the missing
instance (height = ks.length * height B), so varsFixedHeight /
statesFixedHeight / signFixedHeight / constFixedHeight plumbing
disappears (instance bottoms are defeq to the old ones)
- Spa.Analysis.Forward.Evaluation: StmtEvaluator/ExprEvaluator become
classes; the Valid* Props become Prop-classes, as in Agda
- Spa.Analysis.Forward.Adapters: the expr→stmt adapter and its validity
are instances (Agda: the ExprToStmtAdapter instances)
- LatticeInterpretation is a class; sign/const interpretations,
evaluators and validity proofs are instances; use sites read like the
Agda module applications: result SignLattice prog
Proof simplifications (same theorems, proofs factored):
- Spa.Lattice.AboveBelow.monotone₂_of_strict: any ⊥-strict/⊤-dominated
operation on a flat lattice is monotone — replaces the four near-
identical case bashes per analysis (postulates in Agda)
- Spa.Lattice.AboveBelow.interp_sup_of/interp_inf_of: the shared flat-
lattice interpretation case analysis, making interpSign_sup/inf and
interpConst_sup/inf one-liners
lake build green with zero warnings; lake exe spa output verified
byte-identical (diff) to the previous, Agda-verified output.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 23:32:38 -07:00
|
|
|
|
namespace FiniteHeightLattice
|
|
|
|
|
|
|
|
|
|
|
|
variable (α : Type*) [Lattice α] [FiniteHeightLattice α]
|
|
|
|
|
|
|
|
|
|
|
|
/-- Agda: the `⊥` of `Chain.Height`, with the type explicit. -/
|
|
|
|
|
|
def bot : α := (fixedHeight (α := α)).bot
|
|
|
|
|
|
|
|
|
|
|
|
/-- Agda: `⊥≼` for the instance bottom. -/
|
|
|
|
|
|
theorem bot_le (a : α) : bot α ≤ a := FixedHeight.bot_le _ a
|
|
|
|
|
|
|
|
|
|
|
|
end FiniteHeightLattice
|
|
|
|
|
|
|
2026-06-09 18:36:43 -07:00
|
|
|
|
end Spa
|