Files
agda-spa/lean/Spa/Lattice.lean
Danila Fedorin b16f14fdfd 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

169 lines
6.5 KiB
Lean4
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/-
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)
/-- 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
/-- 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. -/
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
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
end Spa