Skip to content

Commit 2feba90

Browse files
committed
refactor: make PreservesSup a unary predicate and himp a def
Rename `SupPreserving R α op` to `PreservesSup (f : α → α)`, a predicate on a single join-preserving map following the Lean-core `Preserves*` convention (`PreservesLE`, `PreservesLeast?`). A frame operator is instantiated at its slice `op r`: the lattice meet at `meet a`, the cost combinator at `costConj r`. `upperAdjoint`/`le_upperAdjoint`/`upperAdjoint_le`/`map_mono` and `frameClosure` follow, with `frameClosure_frames` taking `[∀ r, PreservesSup (op r)]`. Make `himp` a `def` with its own API derived from the upper-adjoint properties (`le_himp`, `meet_himp_le`), `⇨` its notation. `vcgen` decomposes `⇨` through a dedicated `himp` split keyed on `le_himp`; the general residual split is gone, since custom operators register their own and the meet case is `himp`.
1 parent 95052f0 commit 2feba90

15 files changed

Lines changed: 198 additions & 216 deletions

File tree

src/Lean/Elab/Tactic/Do/Internal/VCGen/Context.lean

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public structure VCGen.LatticeSplit where
176176
for a split that applies `relLemma` directly without pointwise distribution. -/
177177
applyLemma : Option Name := none
178178
/-- Rebuild the connective from its fixed parameters `params` (e.g. the frame operator of
179-
`SupPreserving.upperAdjoint`), its operands `as`, and the optional lattice carrier type. Unused when
179+
`PreservesSup.upperAdjoint`), its operands `as`, and the optional lattice carrier type. Unused when
180180
`applyLemma` is `none`. -/
181181
mkLattice : Array Expr → Array Expr → Option Expr → MetaM Expr := fun _ _ _ =>
182182
throwError "LatticeSplit.mkLattice is unavailable for a direct split (applyLemma := none)"
@@ -188,15 +188,11 @@ public structure VCGen.LatticeSplit where
188188
(`(⌜p⌝ : σ→β) s = (⌜p⌝ : β)`, `(⊤ : σ→β) s = (⊤ : β)`), so it must not be applied to `s`. -/
189189
needApplyArgs : Bool := false
190190
/-- The number of fixed parameters before the lattice operands: `1` for the frame operator of
191-
`SupPreserving.upperAdjoint`, `0` for `⊓`/`⌜·⌝`/`⊤`. -/
191+
`PreservesSup.upperAdjoint`, `0` for `⊓`/`⌜·⌝`/`⊤`. -/
192192
numParams : Nat := 0
193193
/-- The number of explicit lattice operands the connective takes after its carrier type,
194194
instance, and parameters: `2` for `⊓`/`⇨`, `1` for `⌜·⌝`, `0` for `⊤`. -/
195195
numOperands : Nat := 0
196-
/-- The number of leading carrier/instance arguments before the connective's parameters: `2` for
197-
`⊓`/`⌜·⌝`/`⊤` (carrier and `CompleteLattice` instance), `3` for `SupPreserving.upperAdjoint` (resource type,
198-
carrier, and instance). -/
199-
leadingArgs : Nat := 2
200196

201197
public structure VCGen.Context where
202198
/-- Pre-built backward rules used by `solve`. -/
@@ -208,7 +204,7 @@ public structure VCGen.Context where
208204
`splitLatticeOp?` before the built-in connectives, so a custom frame proc can decompose
209205
`pre ⊑ conj F rest` for its own `conj`. -/
210206
customLatticeSplits : Std.HashMap Name VCGen.LatticeSplit := {}
211-
/-- Lattice splits for the residual wands `SupPreserving.upperAdjoint conj F rest` of custom frame operators,
207+
/-- Lattice splits for the residual wands `PreservesSup.upperAdjoint conj F rest` of custom frame operators,
212208
keyed by the `conj` head constant. Consulted by `splitLatticeOp?` (dispatching on the inner
213209
operator) so a custom frame's magic wand decomposes instead of surfacing in a VC. -/
214210
customImpSplits : Std.HashMap Name VCGen.LatticeSplit := {}

src/Lean/Elab/Tactic/Do/Internal/VCGen/Entails.lean

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,23 +78,22 @@ public def splitLatticeOp? (goal : MVarId) (rhs : Expr) :
7878
rhs.withApp fun head args => do
7979
let some headName := head.constName? | return none
8080
let ctx ← read
81-
-- For a residual `SupPreserving.upperAdjoint conj a b`, dispatch on the inner operator `conj` (its head): a
82-
-- custom frame's magic wand goes to its own `impSplit`, while the meet `⇨` (whose `conj` is a
83-
-- lambda with no head constant) falls through to the built-in residual split.
81+
-- For a residual `PreservesSup.upperAdjoint f b`, dispatch on the head of the slice `f`: a custom
82+
-- frame's magic wand (`f = conj F`, head `conj`) goes to its own `impSplit`. The meet `⇨` is its
83+
-- own head `himp`, handled by the branch below.
8484
let custom? :=
85-
if headName == ``Lean.Order.SupPreserving.upperAdjoint then
86-
-- `@SupPreserving.upperAdjoint R α inst op r b`: the inner operator `op` is at index 3.
87-
(args[3]?.bind (·.getAppFn.constName?)).bind (ctx.customImpSplits[·]?)
85+
if headName == ``Lean.Order.PreservesSup.upperAdjoint then
86+
-- `@PreservesSup.upperAdjoint α inst f b`: the slice `f` is at index 2.
87+
(args[2]?.bind (·.getAppFn.constName?)).bind (ctx.customImpSplits[·]?)
8888
else
8989
ctx.customLatticeSplits[headName]?
9090
let some c := custom? <|> latticeSplits[headName]? | return none
9191
let rule ← match c.applyLemma with
9292
| none => mkBackwardRuleForLatticeDirectCached c
9393
| some _ =>
94-
let lead := c.leadingArgs
95-
let params := args.extract lead (lead + c.numParams)
96-
let as := args.extract (lead + c.numParams) (lead + c.numParams + c.numOperands)
97-
let excessArgs := args.drop (lead + c.numParams + c.numOperands)
94+
let params := args.extract 2 (2 + c.numParams)
95+
let as := args.extract (2 + c.numParams) (2 + c.numParams + c.numOperands)
96+
let excessArgs := args.drop (2 + c.numParams + c.numOperands)
9897
let resultType? := if c.needApplyArgs then none else args[0]?
9998
mkBackwardRuleForLatticeCached c params as excessArgs resultType?
10099
match ← rule.applyChecked goal with

src/Lean/Elab/Tactic/Do/Internal/VCGen/FrameProc.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ structure FrameProc where
3737
op : WPInfo → MetaM Expr
3838
/-- The lattice split decomposing `conj F R` on the RHS of an entailment. -/
3939
split : LatticeSplit
40-
/-- The lattice split decomposing the residual `SupPreserving.upperAdjoint conj F R` (the frame's magic wand)
40+
/-- The lattice split decomposing the residual `PreservesSup.upperAdjoint conj F R` (the frame's magic wand)
4141
on the RHS of an entailment, so the wand never surfaces in a VC. -/
4242
impSplit : LatticeSplit
4343

@@ -60,7 +60,7 @@ structure FrameProcs where
6060
procs : Std.HashMap Name FrameProc := {}
6161
/-- Splits for the frame operators `conj F R`, keyed by `conj` head. -/
6262
splits : Std.HashMap Name LatticeSplit := {}
63-
/-- Splits for the residual wands `SupPreserving.upperAdjoint conj F R`, keyed by `conj` head. -/
63+
/-- Splits for the residual wands `PreservesSup.upperAdjoint conj F R`, keyed by `conj` head. -/
6464
impSplits : Std.HashMap Name LatticeSplit := {}
6565

6666
instance : Inhabited FrameProcs := ⟨{}⟩

src/Lean/Elab/Tactic/Do/Internal/VCGen/RuleCache.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public def mkBackwardRuleForLatticeCached (c : LatticeSplit) (params as excessAr
7474
(resultType? : Option Expr := none) : VCGenM BackwardRule := do
7575
let s := (← get).latticeBackwardRuleCache
7676
let asTypes ← (as.mapM Sym.inferType : SymM (Array Expr))
77-
-- `params` (e.g. the frame operator of `SupPreserving.upperAdjoint`) is functionally determined by `asTypes`,
77+
-- `params` (e.g. the frame operator of `PreservesSup.upperAdjoint`) is functionally determined by `asTypes`,
7878
-- so it need not enter the cache key.
7979
let key := (c.relLemma, asTypes.map ExprPtr.mk, excessArgs.size)
8080
if let some rule := s[key]? then return rule

src/Lean/Elab/Tactic/Do/Internal/VCGen/RuleConstruction.lean

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,29 +44,13 @@ public def LatticeSplit.meet : LatticeSplit where
4444
numParams := 0
4545
numOperands := 2
4646

47-
/-- The residual `SupPreserving.upperAdjoint op` (with `⇨ = SupPreserving.upperAdjoint (· ⊓ ·)` the meet case). The frame
48-
operator `op` is a fixed parameter; the apply/split lemmas are the meet-specific `himp_apply`/
49-
`le_himp`, since precise framing in `vcgen` is the meet special case. -/
50-
public def LatticeSplit.imp : LatticeSplit where
51-
mkLattice params as _ := mkAppM ``SupPreserving.upperAdjoint (params ++ as)
52-
-- Pointwise distribution is the meet-specific precise-framing lemma; the `⊑`-split is the general
53-
-- residual adjunction, with `op` unified from the goal.
54-
applyLemma := some ``himp_apply
55-
-- le_upperAdjoint (op) {r b x} (h : op r x ⊑ b) : x ⊑ SupPreserving.upperAdjoint op r b
56-
relLemma := ``SupPreserving.le_upperAdjoint
57-
needApplyArgs := true
58-
numParams := 1
59-
numOperands := 2
60-
leadingArgs := 3
61-
62-
/-- Heyting implication `⇨ = himp`, the meet upper adjoint. A specialization of `LatticeSplit.imp`
63-
to the meet operator: `himp` bakes in `(· ⊓ ·)`, so it has no operator parameter and the standard
64-
two leading carrier/instance arguments. -/
47+
/-- Heyting implication `⇨ = himp`, the meet upper adjoint. `himp` bakes in `(· ⊓ ·)`, so it has no
48+
operator parameter; the apply lemma is the meet-specific `himp_apply` and the `⊑`-split is the
49+
upper-adjoint unit `le_upperAdjoint`, with the meet slice unified from the goal. -/
6550
public def LatticeSplit.himp : LatticeSplit where
6651
mkLattice _ as _ := mkAppM ``Lean.Order.himp as
6752
applyLemma := some ``himp_apply
68-
-- le_upperAdjoint (op) {r b x} (h : op r x ⊑ b) : x ⊑ SupPreserving.upperAdjoint op r b ≡ x ⊑ himp r b
69-
relLemma := ``SupPreserving.le_upperAdjoint
53+
relLemma := ``Lean.Order.le_himp -- le_himp {a b x} (h : a ⊓ x ⊑ b) : x ⊑ a ⇨ b
7054
needApplyArgs := true
7155
numParams := 0
7256
numOperands := 2
@@ -96,7 +80,6 @@ public def latticeSplits : Std.HashMap Name LatticeSplit :=
9680
.ofList [
9781
(``meet, .meet),
9882
(``Lean.Order.himp, .himp),
99-
(``SupPreserving.upperAdjoint, .imp),
10083
(``Lean.Order.CompleteLattice.ofProp, .ofProp),
10184
(``Lean.Order.top, .top)]
10285

src/Std/Internal/Do/Order/Heyting.lean

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Authors: Vladimir Gladshtein, Sebastian Graf
66
module
77

88
prelude
9-
public import Std.Internal.Do.Order.SupPreserving
9+
public import Std.Internal.Do.Order.PreservesSup
1010

1111
public section
1212

@@ -16,8 +16,8 @@ universe u
1616

1717
variable {α : Type u} [CompleteLattice α]
1818

19-
instance : SupPreserving Prop Prop (· ⊓ ·) where
20-
op_sup a s := by
19+
instance (a : Prop) : PreservesSup (meet a) where
20+
map_sup s := by
2121
show a ⊓ CompleteLattice.sup s = CompleteLattice.sup (fun y => ∃ x, s x ∧ y = a ⊓ x)
2222
have sup_eq_propSup (c : PropProp) : CompleteLattice.sup c = propSup c := by
2323
apply propext
@@ -36,11 +36,11 @@ instance : SupPreserving Prop Prop (· ⊓ ·) where
3636
exact ⟨hp.1, x, hsx, hp.2
3737

3838
instance {σ : Type v} {β : σ → Type u} [∀ s, CompleteLattice (β s)]
39-
[∀ s, SupPreserving (β s) (β s) (· ⊓ ·)] : SupPreserving (∀ s, β s) (∀ s, β s) (· ⊓ ·) where
40-
op_sup a s := by
39+
[∀ s, ∀ c : β s, PreservesSup (meet c)] (a : ∀ s, β s) : PreservesSup (meet a) where
40+
map_sup s := by
4141
show a ⊓ CompleteLattice.sup s = CompleteLattice.sup (fun y => ∃ x, s x ∧ y = a ⊓ x)
4242
funext t
43-
rw [meet_apply, sup_apply, sup_apply, SupPreserving.op_sup (op := (· ⊓ ·))]
43+
rw [meet_apply, sup_apply, sup_apply, PreservesSup.map_sup (f := meet (a t))]
4444
congr 1
4545
funext w
4646
apply propext
@@ -51,17 +51,25 @@ instance {σ : Type v} {β : σ → Type u} [∀ s, CompleteLattice (β s)]
5151
exact ⟨x t, ⟨x, hx, rfl⟩, by rw [← hgt, meet_apply]⟩
5252

5353
/-- Heyting implication: the upper adjoint of the lattice meet. For `Prop` it is `→`. -/
54-
noncomputable abbrev himp {α : Type u} [CompleteLattice α] (a b : α) : α :=
55-
SupPreserving.upperAdjoint (· ⊓ ·) a b
54+
noncomputable def himp {α : Type u} [CompleteLattice α] (a b : α) : α :=
55+
PreservesSup.upperAdjoint (meet a) b
5656

5757
@[inherit_doc himp] scoped infixr:60 " ⇨ " => himp
5858

59+
/-- Unit for `⇨`, the meet specialization of `PreservesSup.le_upperAdjoint`: `a ⊓ x ⊑ b → x ⊑ a ⇨ b`. -/
60+
theorem le_himp {a b x : α} (h : a ⊓ x ⊑ b) : x ⊑ a ⇨ b := by
61+
unfold himp; exact PreservesSup.le_upperAdjoint (meet a) h
62+
63+
/-- Counit for `⇨`, the meet specialization of `PreservesSup.upperAdjoint_le`: `a ⊓ (a ⇨ b) ⊑ b`. -/
64+
theorem meet_himp_le {a b : α} [PreservesSup (meet a)] : a ⊓ (a ⇨ b) ⊑ b := by
65+
unfold himp; exact PreservesSup.upperAdjoint_le (meet a) b
66+
5967
@[simp] theorem himp_prop_eq_imp (a b : Prop) : ((a ⇨ b : Prop) = (a → b)) := by
6068
apply propext
6169
constructor
6270
· intro hab
6371
have hs : (a ⇨ b : Prop) ⊑ (a → b) := by
64-
unfold himp SupPreserving.upperAdjoint
72+
unfold himp PreservesSup.upperAdjoint
6573
apply sup_le
6674
intro x hx hxTrue haTrue
6775
have hax : a ⊓ x := by
@@ -74,15 +82,15 @@ noncomputable abbrev himp {α : Type u} [CompleteLattice α] (a b : α) : α :=
7482
have hax' : a ∧ (a → b) := by
7583
simpa [meet_prop_eq_and] using hax
7684
exact hax'.right hax'.left
77-
exact (SupPreserving.le_upperAdjoint (· ⊓ ·) (r := a) (b := b) (x := (a → b)) hx) hab
85+
exact (PreservesSup.le_upperAdjoint (meet a) (b := b) (x := (a → b)) hx) hab
7886

7987
/-- Pointwise characterization of Heyting implication on function lattices. -/
8088
@[simp] theorem himp_apply
8189
{σ : Type v} {β : Type u} [CompleteLattice β]
8290
(a b : σ → β) (s : σ) :
8391
(a ⇨ b) s = (a s ⇨ b s) := by
8492
classical
85-
unfold himp SupPreserving.upperAdjoint
93+
unfold himp PreservesSup.upperAdjoint
8694
rw [sup_apply]
8795
apply PartialOrder.rel_antisymm
8896
· apply sup_le

src/Std/Internal/Do/Order/Lemmas.lean

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,16 @@ theorem le_of_le_bot (h : P ⊑ (⊥ : l)) : P ⊑ Q := rel_trans h (bot_le _)
5454
/-! ## Connectives requiring `Frame` -/
5555

5656
section Frame
57-
variable [SupPreserving l l (· ⊓ ·)]
57+
variable [∀ a : l, PreservesSup (meet a)]
5858

59-
theorem le_himp_comm (h : P ⊓ Q ⊑ R) : P ⊑ Q ⇨ R := SupPreserving.le_upperAdjoint (· ⊓ ·) (rel_trans meet_le_comm h)
60-
theorem le_himp_of_meet_le_comm (h : Q ⊓ P ⊑ R) : P ⊑ Q ⇨ R := SupPreserving.le_upperAdjoint (· ⊓ ·) h
59+
theorem le_himp_comm (h : P ⊓ Q ⊑ R) : P ⊑ Q ⇨ R := le_himp (rel_trans meet_le_comm h)
60+
theorem le_himp_of_meet_le_comm (h : Q ⊓ P ⊑ R) : P ⊑ Q ⇨ R := le_himp h
6161
theorem meet_le_of_le_himp (h : P ⊑ Q ⇨ R) : P ⊓ Q ⊑ R := rel_trans
6262
(le_meet _ _ _ (meet_le_right _ _) (meet_le_of_left_le h))
63-
(SupPreserving.upperAdjoint_le (· ⊓ ·) _ _)
63+
meet_himp_le
6464
theorem meet_le_of_le_himp_comm (h : Q ⊑ P ⇨ R) : P ⊓ Q ⊑ R :=
6565
rel_trans meet_le_comm (meet_le_of_le_himp h)
6666
theorem himp_meet_le : (P ⇨ Q) ⊓ P ⊑ Q := meet_le_of_le_himp rel_refl
67-
theorem meet_himp_le : P ⊓ (P ⇨ Q) ⊑ Q := meet_le_of_le_himp_comm rel_refl
6867
theorem le_himp_mp (h₁ : P ⊑ Q ⇨ R) (h₂ : P ⊑ Q) : P ⊑ R :=
6968
le_trans_meet h₂ (meet_le_of_le_himp h₁)
7069

@@ -96,7 +95,7 @@ theorem iSup_mono {α} {Φ Ψ : α → l} (h : ∀ a, Φ a ⊑ Ψ a) : iSup Φ
9695
iSup_le _ _ fun a => rel_trans (h a) (le_iSup _ a)
9796

9897
section Frame
99-
variable [SupPreserving l l (· ⊓ ·)]
98+
variable [∀ a : l, PreservesSup (meet a)]
10099

101100
theorem himp_mono (h1 : Q ⊑ P) (h2 : P' ⊑ Q') : (P ⇨ P') ⊑ Q ⇨ Q' :=
102101
le_himp_comm <| rel_trans (meet_mono_right h1) <| rel_trans himp_meet_le h2
@@ -156,8 +155,8 @@ theorem bot_join : (⊥ : l) ⊔ P = P :=
156155
rel_antisymm (join_le _ _ _ (bot_le _) rel_refl) (right_le_join _ _)
157156
theorem join_bot : P ⊔ (⊥ : l) = P := join_comm.trans bot_join
158157

159-
section SupPreserving
160-
variable [SupPreserving l l (· ⊓ ·)]
158+
section PreservesSup
159+
variable [∀ a : l, PreservesSup (meet a)]
161160

162161
theorem meet_join_left : P ⊓ (Q ⊔ R) = (P ⊓ Q) ⊔ (P ⊓ R) :=
163162
rel_antisymm
@@ -197,7 +196,7 @@ theorem meet_himp_le_meet : P' ⊓ (P' ⇨ Q') ⊑ P' ⊓ Q' :=
197196
theorem meet_le_meet_of_le_himp (hp : P ⊑ P') (hq : Q ⊑ (P' ⇨ Q')) : P ⊓ Q ⊑ P' ⊓ Q' :=
198197
rel_trans (meet_mono hp hq) meet_himp_le_meet
199198

200-
end SupPreserving
199+
end PreservesSup
201200

202201
/-! # Propositional embedding (`CompleteLattice.ofProp`) -/
203202

@@ -261,7 +260,7 @@ theorem ofProp_forall {α} {Φ : α → Prop} :
261260
· exact ofProp_forall_le
262261

263262
section Frame
264-
variable [SupPreserving l l (· ⊓ ·)]
263+
variable [∀ a : l, PreservesSup (meet a)]
265264

266265
theorem himp_ofProp_le {φ₁ φ₂ : Prop} : (⌜φ₁ → φ₂⌝ : l) ⊑ (⌜φ₁⌝ ⇨ ⌜φ₂⌝) :=
267266
le_himp_comm (rel_trans (rel_of_eq ofProp_and) (ofProp_mono (And.elim id)))
@@ -293,7 +292,7 @@ theorem meet_right_comm : (P ⊓ Q) ⊓ R = (P ⊓ R) ⊓ Q := by
293292
/-! # Working with entailment -/
294293

295294
/-- `⊤ ⊑ (P ⇨ Q)` iff `P ⊑ Q`. -/
296-
@[simp] theorem top_le_himp_iff [SupPreserving l l (· ⊓ ·)] (P Q : l) :
295+
@[simp] theorem top_le_himp_iff [∀ a : l, PreservesSup (meet a)] (P Q : l) :
297296
((⊤ : l) ⊑ P ⇨ Q) ↔ (P ⊑ Q) :=
298297
fun h => rel_trans
299298
(le_meet _ _ _ (le_top _) rel_refl)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/-
2+
Copyright (c) 2026 Lean FRO LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Sebastian Graf
5+
-/
6+
module
7+
8+
prelude
9+
public import Std.Internal.Do.Order.Basic
10+
11+
@[expose] public section
12+
13+
namespace Lean.Order
14+
15+
universe u v w
16+
17+
variable {α : Type u} [CompleteLattice α]
18+
19+
/--
20+
`f : α → α` *preserves `Sup`* if it distributes over arbitrary joins:
21+
`f (sup s) = sup { f x | x ∈ s }`. Equivalently `f` is a lower adjoint, so it has an upper adjoint
22+
`PreservesSup.upperAdjoint f`.
23+
24+
A frame operator acts by a `Sup`-preserving map for each resource `r`: the lattice meet `(a ⊓ ·)`,
25+
or a cost combinator `(costConj r)` for a counter resource. The upper adjoint is the corresponding
26+
implication: Heyting `⇨` for the meet, a magic wand for separating conjunction.
27+
-/
28+
class PreservesSup {α : Type u} [CompleteLattice α] (f : α → α) : Prop where
29+
/-- `f` preserves joins. -/
30+
map_sup (s : α → Prop) :
31+
f (CompleteLattice.sup s) = CompleteLattice.sup (fun y => ∃ x, s x ∧ y = f x)
32+
33+
namespace PreservesSup
34+
35+
/-- The upper adjoint of `f`: the join of all `x` with `f x ⊑ b`. For `f = (a ⊓ ·)` this is Heyting
36+
implication `a ⇨ ·`. -/
37+
noncomputable def upperAdjoint (f : α → α) (b : α) : α := CompleteLattice.sup (fun x => f x ⊑ b)
38+
39+
/-- `upperAdjoint f b` is the least upper bound of `{x | f x ⊑ b}` by definition. -/
40+
theorem upperAdjoint_spec (f : α → α) (b : α) : is_sup (fun x : α => f x ⊑ b) (upperAdjoint f b) :=
41+
CompleteLattice.sup_spec (fun x : α => f x ⊑ b)
42+
43+
/-- Unit, free from the definition of `upperAdjoint`: `f x ⊑ b → x ⊑ upperAdjoint f b`. Needs only
44+
`CompleteLattice`. -/
45+
theorem le_upperAdjoint (f : α → α) {b x : α} (h : f x ⊑ b) : x ⊑ upperAdjoint f b :=
46+
le_sup (c := fun x : α => f x ⊑ b) h
47+
48+
/-- Counit (modus ponens), from join preservation: `f (upperAdjoint f b) ⊑ b`. -/
49+
theorem upperAdjoint_le (f : α → α) [PreservesSup f] (b : α) : f (upperAdjoint f b) ⊑ b := by
50+
unfold upperAdjoint
51+
rw [PreservesSup.map_sup (f := f)]
52+
apply sup_le
53+
rintro y ⟨x, hx, rfl⟩
54+
exact hx
55+
56+
/-- Monotonicity of a `Sup`-preserving `f`, derived from join preservation. -/
57+
theorem map_mono (f : α → α) [PreservesSup f] {b b' : α} (h : b ⊑ b') : f b ⊑ f b' := by
58+
have hsup : (CompleteLattice.sup (fun y => y ⊑ b')) = b' :=
59+
is_sup_unique (CompleteLattice.sup_spec _)
60+
(fun x => ⟨fun hb' y hy => PartialOrder.rel_trans hy hb',
61+
fun hy => hy b' PartialOrder.rel_refl⟩)
62+
calc f b ⊑ f (CompleteLattice.sup (fun y => y ⊑ b')) := by
63+
rw [PreservesSup.map_sup (f := f)]; exact le_sup _ ⟨b, h, rfl⟩
64+
_ = f b' := by rw [hsup]
65+
66+
/-- The **frame closure** of a post-transformer `k` with respect to a family of `Sup`-preserving
67+
operators `op r`: the meet over all resources `r` of the `r`-upper-adjoint of `k` framed by `r`. It
68+
internalizes the frame rule into any `k` (see `frameClosure_frames`), with no assumption on `k`. A
69+
weakest precondition built as `frameClosure op (fun Q => bwp x Q E)` satisfies the `op`-frame rule by
70+
construction. -/
71+
noncomputable def frameClosure {R : Type v} {β : Type w} (op : R → α → α)
72+
(k : (β → α) → α) (Q : β → α) : α :=
73+
⨅ r, upperAdjoint (op r) (k (fun a => op r (Q a)))
74+
75+
/-- The frame rule, internalized: for a family of `Sup`-preserving operators `op r` whose resources
76+
compose by `comp` with the action law `op (comp r r') = op r ∘ op r'`, and any post-transformer `k`,
77+
`op F (frameClosure op k Q) ⊑ frameClosure op k (fun a => op F (Q a))`. -/
78+
theorem frameClosure_frames {R : Type v} {β : Type w} (op : R → α → α) [∀ r, PreservesSup (op r)]
79+
(comp : R → R → R) (hact : ∀ r r' a, op (comp r r') a = op r (op r' a))
80+
(k : (β → α) → α) (Q : β → α) (F : R) :
81+
op F (frameClosure op k Q) ⊑ frameClosure op k (fun a => op F (Q a)) := by
82+
apply le_iInf
83+
intro F'
84+
apply le_upperAdjoint (op F')
85+
rw [← hact F' F (frameClosure op k Q)]
86+
refine PartialOrder.rel_trans (map_mono (op (comp F' F)) (iInf_le _ (comp F' F))) ?_
87+
refine PartialOrder.rel_trans (upperAdjoint_le (op (comp F' F)) _) ?_
88+
apply PartialOrder.rel_of_eq
89+
congr 1
90+
funext a
91+
rw [hact F' F (Q a)]
92+
93+
end PreservesSup
94+
95+
end Lean.Order
96+
97+
end -- public section

0 commit comments

Comments
 (0)