|
| 1 | +/- |
| 2 | +Copyright (c) 2026 Ching-Tsun Chou. All rights reserved. |
| 3 | +Released under Apache 2.0 license as described in the file LICENSE. |
| 4 | +Authors: Ching-Tsun Chou |
| 5 | +-/ |
| 6 | + |
| 7 | +module |
| 8 | + |
| 9 | +public import Cslib.Computability.Distributed.FLP.Consensus |
| 10 | +public import Cslib.Foundations.Data.OmegaSequence.InfOcc |
| 11 | +public import Mathlib.Data.List.ReduceOption |
| 12 | + |
| 13 | +/-! # Machinery for constructing infinite fair executions |
| 14 | +
|
| 15 | +The main goal of this file is to define a `fairScheduler` that, given a function `d` |
| 16 | +of type `DeliverMsg`, a state predicate `q`, and a state `s0` of an algorithm `a`, |
| 17 | +constructs an infinite execution of `a` starting from state `s0` in which all processes |
| 18 | +from a set `ps` are fair and `q` is true infinitely often. With additional assumptions, |
| 19 | +we may also want to require that all actions in the infinite execution satisfy an action |
| 20 | +predicate `r`. |
| 21 | +-/ |
| 22 | + |
| 23 | +@[expose] public section |
| 24 | + |
| 25 | +namespace Cslib.FLP |
| 26 | + |
| 27 | +open Function Set Multiset Filter ωSequence |
| 28 | + |
| 29 | +variable {P M S : Type*} [DecidableEq P] [DecidableEq M] |
| 30 | + |
| 31 | +/-- Given a state `s` and a message `m`, a function `d` of type `DeliverMsg` is supposed to |
| 32 | +return `(xs, t)` where `xs` is a finite execution from `s` to `t` in which `m` is delivered. -/ |
| 33 | +def DeliverMsg P M S := State P M S → Message P M → List (Action P M) × State P M S |
| 34 | + |
| 35 | +/-- `d.ForallActions r` requires that all actions returned by `d` satisfy `r`. -/ |
| 36 | +def DeliverMsg.ForallActions (d : DeliverMsg P M S) (r : Action P M → Prop) : Prop := |
| 37 | + ∀ s m, (d s m).fst.Forall r |
| 38 | + |
| 39 | +/-- `d.foldList s ml ms` uses `d` to deliver all messages that are in `ml` but not in `ms` from |
| 40 | +state `s`. Note that if a message `m` in `ml` is delivered during the delivery of an earlier |
| 41 | +message, `m` is added to `ms` so that it is not processed again. -/ |
| 42 | +def DeliverMsg.foldList (d : DeliverMsg P M S) (s : State P M S) : |
| 43 | + List (Message P M) → Finset (Message P M) → List (Action P M) × State P M S |
| 44 | + | [], _ => ([], s) |
| 45 | + | m :: ml, ms => |
| 46 | + if m ∈ ms then |
| 47 | + d.foldList s ml ms |
| 48 | + else |
| 49 | + let (xl1, s1) := d s m |
| 50 | + let ms' := ms ∪ xl1.reduceOption.toFinset |
| 51 | + let (xl2, s2) := d.foldList s1 ml ms' |
| 52 | + (xl1 ++ xl2, s2) |
| 53 | + |
| 54 | +open scoped Classical in |
| 55 | +/-- `d.scheduleMsgs ps s` schedules and delivers all messages which are in-flight in state `s` |
| 56 | +and have destinations in `ps` in some order (as determined by choice). If no such message exists, |
| 57 | +then the the stuttering step is taken. -/ |
| 58 | +noncomputable def DeliverMsg.scheduleMsgs (d : DeliverMsg P M S) (ps : Set P) |
| 59 | + (s : State P M S) : List (Action P M) × State P M S := |
| 60 | + let ms := s.msgs.filter (fun m ↦ m.dest ∈ ps) |
| 61 | + if ms = 0 then |
| 62 | + ([none], s) |
| 63 | + else |
| 64 | + d.foldList s ms.toList ∅ |
| 65 | + |
| 66 | +namespace DeliverMsg |
| 67 | + |
| 68 | +variable {d : DeliverMsg P M S} |
| 69 | + |
| 70 | +/-- If `d.ForallActions r`, then `d.foldList s ml ms` can only use actions satisfying `r`. -/ |
| 71 | +theorem foldList_forallActions {r : Action P M → Prop} |
| 72 | + (s : State P M S) (ml : List (Message P M)) (ms : Finset (Message P M)) |
| 73 | + (h : d.ForallActions r) : (d.foldList s ml ms).fst.Forall r := by |
| 74 | + induction ml generalizing s ms <;> |
| 75 | + grind [DeliverMsg.foldList, DeliverMsg.ForallActions, List.Forall, List.forall_append] |
| 76 | + |
| 77 | +end DeliverMsg |
| 78 | + |
| 79 | +/-- Starting from state `s0`, `a.fairSchedular d ps s0` constructs an infinite sequence of |
| 80 | +finite executions of `a` by repeatedly applying `d.scheduleMsgs ps`. -/ |
| 81 | +noncomputable def Algorithm.fairScheduler (a : Algorithm P M S) (d : DeliverMsg P M S) (ps : Set P) |
| 82 | + (s0 : State P M S) : ℕ → List (Action P M) × State P M S |
| 83 | + | 0 => ([], s0) |
| 84 | + | k + 1 => d.scheduleMsgs ps (a.fairScheduler d ps s0 k).snd |
| 85 | + |
| 86 | +/-- The infinite sequence of states forming the end states of the finite executions constructed |
| 87 | +by `Algorithm.fairScheduler`. -/ |
| 88 | +noncomputable def Algorithm.fairSegEnds (a : Algorithm P M S) (d : DeliverMsg P M S) |
| 89 | + (ps : Set P) (s0 : State P M S) : ωSequence (State P M S) := |
| 90 | + ωSequence.mk (fun k ↦ (a.fairScheduler d ps s0 k).snd) |
| 91 | + |
| 92 | +/-- The infinite sequence of finite action sequences from the finite executions constructed |
| 93 | +by `Algorithm.fairScheduler`. -/ |
| 94 | +noncomputable def Algorithm.fairSegActions (a : Algorithm P M S) (d : DeliverMsg P M S) |
| 95 | + (ps : Set P) (s0 : State P M S) : ωSequence (List (Action P M)) := |
| 96 | + (ωSequence.mk (fun k ↦ (a.fairScheduler d ps s0 k).fst)).tail |
| 97 | + |
| 98 | +/-- `a.FairDeliverMsg d ps q` says that for any state `s` of `a` satisfying `q` and |
| 99 | +any message `m` which is in-flight in `s` and whose destination is in `ps`, `d s m` |
| 100 | +produces a legal finite execution of `a` in which `m` is delivered and which ends in |
| 101 | +a state satisfying `q` again. -/ |
| 102 | +def Algorithm.FairDeliverMsg (a : Algorithm P M S) (d : DeliverMsg P M S) |
| 103 | + (ps : Set P) (q : State P M S → Prop) : Prop := |
| 104 | + ∀ s m, m ∈ s.msgs ∧ m.dest ∈ ps ∧ q s → |
| 105 | + let (xl, t) := d s m |
| 106 | + a.lts.MTr s xl t ∧ some m ∈ xl ∧ q t |
| 107 | + |
| 108 | +namespace FairScheduler |
| 109 | + |
| 110 | +variable {a : Algorithm P M S} |
| 111 | + |
| 112 | +/-- Re-stating the definition of `Algorithm.fairScheduler` as a mutual recursion of |
| 113 | +`Algorithm.fairSegEnds` and `Algorithm.fairSegActions`. -/ |
| 114 | +theorem fairScheduler_init {d : DeliverMsg P M S} (ps : Set P) (s0 : State P M S) : |
| 115 | + a.fairSegEnds d ps s0 0 = s0 := by |
| 116 | + grind [Algorithm.fairScheduler, Algorithm.fairSegEnds] |
| 117 | + |
| 118 | +/-- Re-stating the definition of `Algorithm.fairScheduler` as a mutual recursion of |
| 119 | +`Algorithm.fairSegEnds` and `Algorithm.fairSegActions`. -/ |
| 120 | +theorem fairScheduler_step {d : DeliverMsg P M S} (ps : Set P) (s0 : State P M S) (k : ℕ) : |
| 121 | + d.scheduleMsgs ps (a.fairSegEnds d ps s0 k) = |
| 122 | + (a.fairSegActions d ps s0 k, a.fairSegEnds d ps s0 (k + 1)) := by |
| 123 | + grind [Algorithm.fairScheduler, Algorithm.fairSegEnds, Algorithm.fairSegActions] |
| 124 | + |
| 125 | +/-- If `d.ForallActions r`, then `a.fairSegActions d ps s0` can only use actions satisfying `r`. -/ |
| 126 | +theorem fairSeg_forallActions {d : DeliverMsg P M S} {r : Action P M → Prop} |
| 127 | + (ps : Set P) (s0 : State P M S) (k : ℕ) (ha : d.ForallActions r) (hn : r none) : |
| 128 | + (a.fairSegActions d ps s0 k).Forall r := by |
| 129 | + grind [fairScheduler_step (a := a) (d := d) ps s0 k, |
| 130 | + DeliverMsg.scheduleMsgs, DeliverMsg.foldList_forallActions, List.Forall] |
| 131 | + |
| 132 | +/-- The correctness of `d.foldList s ml ms` under the assumption `a.FairDeliverMsg d ps q`. -/ |
| 133 | +theorem fairDeliverMsg_foldList {d : DeliverMsg P M S} {ps : Set P} {q : State P M S → Prop} |
| 134 | + (hd : a.FairDeliverMsg d ps q) (s : State P M S) |
| 135 | + (ml : List (Message P M)) (ms : Finset (Message P M)) |
| 136 | + (hs : q s ∧ ∀ m, m ∈ ml → ¬ m ∈ ms → m ∈ s.msgs ∧ m.dest ∈ ps) : |
| 137 | + let (xl, t) := d.foldList s ml ms |
| 138 | + a.lts.MTr s xl t ∧ q t ∧ ∀ m, m ∈ ml → ¬ m ∈ ms → some m ∈ xl := by |
| 139 | + induction ml generalizing s ms |
| 140 | + case nil => grind [DeliverMsg.foldList, LTS.MTr] |
| 141 | + case cons m ml h_ind => |
| 142 | + by_cases h_m : m ∈ ms |
| 143 | + · grind [DeliverMsg.foldList] |
| 144 | + · let xl1 := (d s m).fst |
| 145 | + let s1 := (d s m).snd |
| 146 | + let ms' := ms ∪ xl1.reduceOption.toFinset |
| 147 | + have (m' : Message P M) : m' ∈ xl1.reduceOption.toFinset ↔ some m' ∈ xl1 := by |
| 148 | + simp [List.mem_toFinset, List.reduceOption_mem_iff] |
| 149 | + have (m' : Message P M) : m' ∈ ml → ¬ m' ∈ ms' → m' ∈ s1.msgs := by |
| 150 | + grind [Algorithm.FairDeliverMsg, Algorithm.mTr_notRcvd_enabled] |
| 151 | + grind [DeliverMsg.foldList, Algorithm.FairDeliverMsg, LTS.MTr.comp] |
| 152 | + |
| 153 | +/-- The correctness of `d.scheduleMsgs ps s` under the assumption `a.FairDeliverMsg d ps q`. -/ |
| 154 | +theorem fairDeliverMsg_scheduleMsgs {d : DeliverMsg P M S} {ps : Set P} {q : State P M S → Prop} |
| 155 | + (hd : a.FairDeliverMsg d ps q) (s : State P M S) (hs : q s) : |
| 156 | + let xl := (d.scheduleMsgs ps s).fst |
| 157 | + let t := (d.scheduleMsgs ps s).snd |
| 158 | + q t ∧ a.lts.MTr s xl t ∧ xl.length > 0 ∧ ∀ m, m ∈ s.msgs → m.dest ∈ ps → some m ∈ xl := by |
| 159 | + classical |
| 160 | + intro xl t |
| 161 | + let ms := s.msgs.filter (fun m ↦ m.dest ∈ ps) |
| 162 | + by_cases h_ms : ms = 0 |
| 163 | + · have h1 : xl = [none] ∧ t = s := by grind [DeliverMsg.scheduleMsgs] |
| 164 | + simp [ms, eq_zero_iff_forall_notMem] at h_ms |
| 165 | + split_ands <;> try grind |
| 166 | + simp only [h1] |
| 167 | + apply LTS.MTr.single |
| 168 | + grind [Algorithm.lts] |
| 169 | + · have : q t ∧ a.lts.MTr s xl t ∧ ∀ m, m ∈ ms.toList → some m ∈ xl := by |
| 170 | + grind [DeliverMsg.scheduleMsgs, fairDeliverMsg_foldList hd s ms.toList ∅ (by simp [ms, hs])] |
| 171 | + split_ands <;> try grind [mem_toList, mem_filter] |
| 172 | + obtain ⟨m, h_ms⟩ := exists_mem_of_ne_zero h_ms |
| 173 | + suffices some m ∈ xl by grind |
| 174 | + grind [mem_toList] |
| 175 | + |
| 176 | +/-- The correctness of `a.fairSegEnds d ps s0` and `a.fairSegActions d ps s0` |
| 177 | +under the assumption `a.FairDeliverMsg d ps q`. -/ |
| 178 | +theorem fair_fairSegs {d : DeliverMsg P M S} {ps : Set P} {q : State P M S → Prop} |
| 179 | + (hd : a.FairDeliverMsg d ps q) (s0 : State P M S) (hs0 : q s0) : |
| 180 | + let ts := a.fairSegEnds d ps s0 |
| 181 | + let xls := a.fairSegActions d ps s0 |
| 182 | + ∀ k, q (ts k) ∧ a.lts.MTr (ts k) (xls k) (ts (k + 1)) ∧ (xls k).length > 0 ∧ |
| 183 | + ∀ m, m ∈ (ts k).msgs → m.dest ∈ ps → some m ∈ xls k := by |
| 184 | + classical |
| 185 | + intro ts xls k |
| 186 | + induction k <;> grind [fairScheduler_init, fairScheduler_step, fairDeliverMsg_scheduleMsgs] |
| 187 | + |
| 188 | +/-- Given an infinite sequence of non-empty finite executions of algorithm `a`, |
| 189 | +if all messages with destinations in `ps` that are in-flight at the beginning of each |
| 190 | +finite execution are delivered in that finite execution, then those finite executions can |
| 191 | +be concatenated into an infinite execution of `a` in which every process in `ps` is fair. -/ |
| 192 | +theorem flatten_fairSegs {ps : Set P} |
| 193 | + {ts : ωSequence (State P M S)} {xls : ωSequence (List (Action P M))} |
| 194 | + (hmtr : ∀ k, a.lts.MTr (ts k) (xls k) (ts (k + 1))) |
| 195 | + (hpos : ∀ k, (xls k).length > 0) |
| 196 | + (hsch : ∀ k m, m ∈ (ts k).msgs → m.dest ∈ ps → some m ∈ xls k) : |
| 197 | + ∃ ss, a.lts.OmegaExecution ss xls.flatten ∧ (∀ k, ss (xls.cumLen k) = ts k) ∧ |
| 198 | + ∀ p, p ∈ ps → ProcFair p ss xls.flatten := by |
| 199 | + obtain ⟨ss, h_omega, h_ts⟩ := LTS.OmegaExecution.flatten_mTr hmtr hpos |
| 200 | + use ss, h_omega, h_ts |
| 201 | + rintro p h_m m ⟨rfl⟩ |
| 202 | + by_contra! ⟨k, h_k, h_k'⟩ |
| 203 | + have h_xls : ∃ᶠ n in atTop, n ∈ xls.cumLen '' univ := by |
| 204 | + apply frequently_iff_strictMono.mpr |
| 205 | + use xls.cumLen |
| 206 | + grind [cumLen_strictMono] |
| 207 | + obtain ⟨j, _, h_j⟩ : ∃ j, k ≤ xls.cumLen j ∧ m ∈ (ts j).msgs := by |
| 208 | + obtain ⟨n, _, j, _, _⟩ := frequently_atTop.mp h_xls k |
| 209 | + grind [Algorithm.omega_notRcvd_enabled h_omega h_k h_k'] |
| 210 | + obtain ⟨i, _, _⟩ := List.getElem_of_mem <| hsch j m h_j h_m |
| 211 | + grind [extract_flatten hpos j] |
| 212 | + |
| 213 | +/-- Under the assumption `a.FairDeliverMsg d ps q`, the infinite sequence of finite executions |
| 214 | +of `a` represented by `a.fairSegEnds d ps s0` and `a.fairSegActions d ps s0` can be concatenated |
| 215 | +into an infinite execution of `a` in which every process in `ps` is fair and `q` is true at |
| 216 | +the ends of all those finite executions. -/ |
| 217 | +theorem fair_omegaExecution {d : DeliverMsg P M S} {ps : Set P} {q : State P M S → Prop} |
| 218 | + (hd : a.FairDeliverMsg d ps q) (s0 : State P M S) (hs0 : q s0) : |
| 219 | + let ts := a.fairSegEnds d ps s0 |
| 220 | + let xls := a.fairSegActions d ps s0 |
| 221 | + ∃ ss, a.lts.OmegaExecution ss xls.flatten ∧ |
| 222 | + ss 0 = s0 ∧ (∀ k, ss (xls.cumLen k) = ts k) ∧ |
| 223 | + (∀ k, q (ss (xls.cumLen k))) ∧ (∀ k, (xls k).length > 0) ∧ |
| 224 | + ∀ p, p ∈ ps → ProcFair p ss xls.flatten := by |
| 225 | + intro ts xls |
| 226 | + obtain ⟨h_q, hmtr, hpos, hsch⟩ : |
| 227 | + (∀ k, q (ts k)) ∧ |
| 228 | + (∀ k, a.lts.MTr (ts k) (xls k) (ts (k + 1))) ∧ |
| 229 | + (∀ k, (xls k).length > 0) ∧ |
| 230 | + (∀ k m, m ∈ (ts k).msgs → m.dest ∈ ps → some m ∈ xls k) := by |
| 231 | + grind [fair_fairSegs hd s0 hs0] |
| 232 | + obtain ⟨ss, _, _, _⟩ := flatten_fairSegs hmtr hpos hsch |
| 233 | + have : ss 0 = s0 := by grind [fairScheduler_init] |
| 234 | + use ss |
| 235 | + grind |
| 236 | + |
| 237 | +/-- If `d.ForallActions r`, then the concatenation of all `a.fairSegActions d ps s0` segments |
| 238 | +can only use actions satisfying `r`. -/ |
| 239 | +theorem omega_forall_actions {d : DeliverMsg P M S} {ps : Set P} |
| 240 | + {q : State P M S → Prop} {r : Action P M → Prop} |
| 241 | + (hd : a.FairDeliverMsg d ps q) (s0 : State P M S) (hs0 : q s0) |
| 242 | + (ha : d.ForallActions r) (hn : r none) : |
| 243 | + ∀ k, r ((a.fairSegActions d ps s0).flatten k) := by |
| 244 | + have hpos : ∀ k, (a.fairSegActions d ps s0 k).length > 0 := by grind [fair_fairSegs hd s0 hs0] |
| 245 | + simp only [forall_flatten_iff hpos] |
| 246 | + grind [fairSeg_forallActions] |
| 247 | + |
| 248 | +end FairScheduler |
| 249 | + |
| 250 | +end Cslib.FLP |
0 commit comments