@bramas/simulant-lean 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,52 @@
1
+ import Simulant.NonVacuity
2
+ import Simulant.Library.BlackHole
3
+
4
+ /-!
5
+ # Lemmas about the black hole, for every model that contains it
6
+
7
+ Hand-written, once per library version (plan, Section 5.1). They are
8
+ stated for any list of components, given that the other components
9
+ preserve the black hole's part of the world; a generated model file
10
+ proves that hypothesis (`others_preserve_BlackHole`), because components
11
+ only write their own part.
12
+ -/
13
+
14
+ namespace Simulant.Library.BlackHole
15
+ open Simulant
16
+
17
+ variable {σ : Sig} [FlagSet σ.F] [Flags σ.F]
18
+
19
+ /-- The black hole's own effects never change its site. -/
20
+ theorem hooks_keep_site (policy : HolePolicy) (L : Ctx σ) :
21
+ (∀ ts C e, ND.Can ((hooks policy L).beforeLook ts C) e → ∀ x, (e.world x).site = x.site) ∧
22
+ (∀ ts C ds e, ND.Can ((hooks policy L).beforeMove ts C ds) e → ∀ x, (e.world x).site = x.site) ∧
23
+ (∀ ts C e, ND.Can ((hooks policy L).afterMove ts C) e → ∀ x, (e.world x).site = x.site) := by
24
+ refine ⟨fun ts C e he x => ?_, fun ts C ds e he x => ?_, fun ts C e he x => ?_⟩
25
+ · rw [ND.can_pure.1 he]; rfl
26
+ · rw [ND.can_pure.1 he]; rfl
27
+ · obtain ⟨_, -, rfl⟩ := ND.can_map.1 he; rfl
28
+
29
+ /-- The site of the black hole never changes, whatever the other components. -/
30
+ theorem site_stays (m : Model σ) (act0 : σ.A) (policy : HolePolicy) (L : Ctx σ) [LawfulLens L.world]
31
+ (hc : ∀ c ∈ m.components, c = component policy L ∨ c.Preserves L.world.get)
32
+ {C C' : Config σ} {w : Witness} (hs : m.Step act0 C w C') :
33
+ (L.world.get C'.world).site = (L.world.get C.world).site :=
34
+ m.step_preserves act0 (g := fun w => (L.world.get w).site) (fun c hcm => by
35
+ rcases hc c hcm with rfl | hp
36
+ · exact Hooks.toComponent_preserves_own (hooks policy L) L.world (fun x : World σ.N => x.site)
37
+ (hooks_keep_site policy L)
38
+ · exact hp.comp (·.site)) hs
39
+
40
+ /-- Every hook of the black hole always has an outcome. -/
41
+ theorem component_total (policy : HolePolicy) (L : Ctx σ) : (component policy L).Total := by
42
+ refine ⟨fun ts C => ⟨_, ND.can_bind_of (ND.can_pure.2 rfl) (ND.can_pure.2 rfl)⟩,
43
+ fun ts C ds => ⟨_, ND.can_bind_of (ND.can_pure.2 rfl) (ND.can_pure.2 rfl)⟩, fun ts C => ?_⟩
44
+ unfold component Hooks.toComponent hooks
45
+ dsimp only
46
+ by_cases h : (policy == HolePolicy.mayEmerge && !(L.world.get C.world).active) = true
47
+ · exact ⟨_, ND.can_bind_of (ND.can_bind_of (by simp only [h, ↓reduceIte]; exact ND.can_choose.2 (List.mem_cons_self))
48
+ (ND.can_pure.2 rfl)) (ND.can_pure.2 rfl)⟩
49
+ · exact ⟨_, ND.can_bind_of (ND.can_bind_of (by simp only [h, Bool.false_eq_true, ↓reduceIte]; exact ND.can_pure.2 rfl)
50
+ (ND.can_pure.2 rfl)) (ND.can_pure.2 rfl)⟩
51
+
52
+ end Simulant.Library.BlackHole
@@ -0,0 +1,88 @@
1
+ /-!
2
+ # Nondeterminism with witnesses
3
+
4
+ The adversary's choices form a list monad: a computation is the list of
5
+ its outcomes, each with the witness of the choices that lead to it. A
6
+ witness is a list of (site, index) pairs, the same as the interpreter's
7
+ witnesses without their display labels.
8
+
9
+ The step relation of a model is membership in this list, so the
10
+ computable round and the relational semantics are the same object.
11
+ -/
12
+
13
+ namespace Simulant
14
+
15
+ abbrev Witness := List (String × Nat)
16
+
17
+ /-- A nondeterministic computation: its outcomes, with their witnesses. -/
18
+ abbrev ND (α : Type) : Type := List (Witness × α)
19
+
20
+ namespace ND
21
+
22
+ def pure (a : α) : ND α := [([], a)]
23
+
24
+ def bind (m : ND α) (f : α → ND β) : ND β :=
25
+ List.flatMap (fun (w, a) => (f a).map fun (w', b) => (w ++ w', b)) m
26
+
27
+ instance : Monad ND where
28
+ pure := ND.pure
29
+ bind := ND.bind
30
+
31
+ /-- An adversary choice among `xs`; the witness records the index. -/
32
+ def choose (site : String) (xs : List α) : ND α :=
33
+ (xs.zipIdx).map fun (x, i) => ([(site, i)], x)
34
+
35
+ /-- A choice among the elements of `xs` satisfying `p`; indices are positions in `xs`. -/
36
+ def chooseSuch (site : String) (xs : List α) (p : α → Bool) : ND α :=
37
+ ((xs.zipIdx).filter fun (x, _) => p x).map fun (x, i) => ([(site, i)], x)
38
+
39
+ /-- `m` can produce `a`, with some witness. -/
40
+ def Can (m : ND α) (a : α) : Prop := ∃ w, (w, a) ∈ m
41
+
42
+ /-- The outcomes, forgetting witnesses. -/
43
+ def outcomes (m : ND α) : List α := List.map Prod.snd m
44
+
45
+ @[simp] theorem mem_pure {a b : α} {w : Witness} : (w, b) ∈ (Pure.pure a : ND α) ↔ w = [] ∧ b = a := by
46
+ show (w, b) ∈ [([], a)] ↔ _
47
+ simp
48
+
49
+ theorem mem_bind {m : ND α} {f : α → ND β} {w : Witness} {b : β} :
50
+ (w, b) ∈ (m >>= f) ↔ ∃ w₁ a w₂, (w₁, a) ∈ m ∧ (w₂, b) ∈ f a ∧ w = w₁ ++ w₂ := by
51
+ show (w, b) ∈ ND.bind m f ↔ _
52
+ simp only [ND.bind, List.mem_flatMap, List.mem_map, Prod.exists, Prod.mk.injEq]
53
+ constructor
54
+ · rintro ⟨w₁, a, hm, w₂, b', hf, rfl, rfl⟩
55
+ exact ⟨w₁, a, w₂, hm, hf, rfl⟩
56
+ · rintro ⟨w₁, a, w₂, hm, hf, rfl⟩
57
+ exact ⟨w₁, a, hm, w₂, b, hf, rfl, rfl⟩
58
+
59
+ theorem can_pure {a b : α} : Can (Pure.pure a : ND α) b ↔ b = a := by
60
+ simp [Can]
61
+
62
+ theorem can_bind {m : ND α} {f : α → ND β} {b : β} :
63
+ Can (m >>= f) b ↔ ∃ a, Can m a ∧ Can (f a) b := by
64
+ simp only [Can, mem_bind]
65
+ constructor
66
+ · rintro ⟨w, w₁, a, w₂, hm, hf, rfl⟩
67
+ exact ⟨a, ⟨w₁, hm⟩, ⟨w₂, hf⟩⟩
68
+ · rintro ⟨a, ⟨w₁, hm⟩, ⟨w₂, hf⟩⟩
69
+ exact ⟨w₁ ++ w₂, w₁, a, w₂, hm, hf, rfl⟩
70
+
71
+ theorem mem_choose {site : String} {xs : List α} {w : Witness} {x : α} :
72
+ (w, x) ∈ choose site xs ↔ ∃ i, (x, i) ∈ xs.zipIdx ∧ w = [(site, i)] := by
73
+ simp only [choose, List.mem_map, Prod.exists, Prod.mk.injEq]
74
+ constructor
75
+ · rintro ⟨y, i, h, rfl, rfl⟩; exact ⟨i, h, rfl⟩
76
+ · rintro ⟨i, h, rfl⟩; exact ⟨x, i, h, rfl, rfl⟩
77
+
78
+ theorem can_choose {site : String} {xs : List α} {x : α} : Can (choose site xs) x ↔ x ∈ xs := by
79
+ simp only [Can, mem_choose, List.mem_zipIdx_iff_getElem?]
80
+ constructor
81
+ · rintro ⟨_, i, h, -⟩
82
+ exact List.mem_iff_getElem?.2 ⟨i, h⟩
83
+ · intro h
84
+ obtain ⟨i, hi⟩ := List.mem_iff_getElem?.1 h
85
+ exact ⟨_, i, hi, rfl⟩
86
+
87
+ end ND
88
+ end Simulant
@@ -0,0 +1,138 @@
1
+ import Simulant.Lemmas
2
+
3
+ /-!
4
+ # Non-vacuity from totality (plan, Section 5.2)
5
+
6
+ A round built from pieces that always have an outcome always has a
7
+ successor; with an initial configuration, a synchronous round then has an
8
+ admissible execution, so its correctness theorem is not vacuous.
9
+ -/
10
+
11
+ namespace Simulant
12
+
13
+ variable {σ : Sig} [FlagSet σ.F]
14
+
15
+ /-- Every hook always has an outcome. -/
16
+ def Component.Total (c : Component σ) : Prop :=
17
+ (∀ ts C, ∃ e, ND.Can (c.beforeLook ts C) e) ∧
18
+ (∀ ts C ds, ∃ e, ND.Can (c.beforeMove ts C ds) e) ∧
19
+ (∀ ts C, ∃ e, ND.Can (c.afterMove ts C) e)
20
+
21
+ theorem ND.can_of_mem {m : ND α} {w : Witness} {a : α} (h : (w, a) ∈ m) : ND.Can m a := ⟨w, h⟩
22
+
23
+ theorem ND.exists_can {m : ND α} (h : m ≠ []) : ∃ a, ND.Can m a := by
24
+ obtain ⟨⟨w, a⟩, rest, hm⟩ := List.exists_cons_of_ne_nil h
25
+ exact ⟨a, w, by rw [hm]; exact List.mem_cons_self⟩
26
+
27
+ theorem ND.can_bind_of {m : ND α} {f : α → ND β} {a : α} {b : β} (h1 : ND.Can m a) (h2 : ND.Can (f a) b) :
28
+ ND.Can (m >>= f) b :=
29
+ ND.can_bind.2 ⟨a, h1, h2⟩
30
+
31
+ theorem chooseIfMany_total (site : String) {l : List α} (h : l ≠ []) : ∃ x, ND.Can (chooseIfMany site l) x := by
32
+ unfold chooseIfMany
33
+ split
34
+ · exact ⟨_, ND.can_pure.2 rfl⟩
35
+ · obtain ⟨x, hx⟩ := List.exists_mem_of_ne_nil l h
36
+ exact ⟨x, ND.can_choose.2 hx⟩
37
+
38
+ theorem parallel_total (C : Config σ) :
39
+ ∀ (hooks : List (Config σ → ND (Effect σ))), (∀ hk ∈ hooks, ∃ e, ND.Can (hk C) e) →
40
+ ∃ e, ND.Can (parallel hooks C) e
41
+ | [], _ => ⟨_, ND.can_pure.2 rfl⟩
42
+ | hk :: hs, h => by
43
+ obtain ⟨e, he⟩ := h hk List.mem_cons_self
44
+ obtain ⟨r, hr⟩ := parallel_total C hs (fun hk' hm => h hk' (List.mem_cons_of_mem _ hm))
45
+ exact ⟨_, ND.can_bind_of he (ND.can_bind_of hr (ND.can_pure.2 rfl))⟩
46
+
47
+ theorem combos_one (l : List α) : combos 1 l = l.map ([·]) := by
48
+ induction l with
49
+ | nil => rfl
50
+ | cons x xs ih => simp [combos, ih]
51
+
52
+ namespace Model
53
+
54
+ variable (m : Model σ) (act0 : σ.A)
55
+
56
+ theorem activate_total (C : Config σ) : ∃ a, ND.Can (m.activate C) a := by
57
+ unfold Model.activate
58
+ dsimp only
59
+ split
60
+ · exact ⟨_, ND.can_pure.2 rfl⟩
61
+ · split
62
+ · exact ⟨_, ND.can_pure.2 rfl⟩
63
+ · rename_i h
64
+ obtain ⟨x, hx⟩ := List.exists_mem_of_ne_nil _ (fun he => h (List.isEmpty_iff.2 he))
65
+ refine ⟨[x], ND.can_choose.2 ?_⟩
66
+ simp only [nonemptySubsets, List.mem_flatMap, List.mem_range]
67
+ exact ⟨0, List.length_pos_of_mem hx, by rw [combos_one]; exact List.mem_map_of_mem hx⟩
68
+ · split
69
+ · exact ⟨_, ND.can_pure.2 rfl⟩
70
+ · rename_i h
71
+ obtain ⟨x, hx⟩ := List.exists_mem_of_ne_nil _ (fun he => h (List.isEmpty_iff.2 he))
72
+ exact ⟨[x], ND.can_choose.2 (List.mem_map_of_mem hx)⟩
73
+
74
+ omit [FlagSet σ.F] in
75
+ theorem frames_ne_nil (a : Agent σ) : m.frames a ≠ [] := by
76
+ unfold Model.frames; split <;> simp
77
+
78
+ theorem options_total (ts : TState) (C : Config σ) (i : Fin σ.k)
79
+ (hview : ∀ f, ∃ v, ND.Can (m.view ts C i f) v) : ∃ d, ND.Can (m.options act0 ts C i) d := by
80
+ unfold Model.options
81
+ dsimp only
82
+ split
83
+ · exact ⟨_, ND.can_bind_of (ND.can_choose.2 (List.mem_cons_self)) (ND.can_pure.2 rfl)⟩
84
+ · obtain ⟨f, hf⟩ := chooseIfMany_total ("frame " ++ agentName i) (m.frames_ne_nil C.agents[i])
85
+ obtain ⟨v, hv⟩ := hview f
86
+ exact ⟨_, ND.can_bind_of hf (ND.can_bind_of hv (ND.can_pure.2 rfl))⟩
87
+
88
+ theorem decideAll_total (ts : TState) (C : Config σ) (hview : ∀ i f, ∃ v, ND.Can (m.view ts C i f) v) :
89
+ ∀ (active : List (Fin σ.k)) (init : Fin σ.k → Decision σ),
90
+ ∃ ds, ND.Can (active.foldlM (fun ds i => do
91
+ let d ← m.options act0 ts C i
92
+ pure fun j => if j = i then d else ds j) init) ds
93
+ | [], init => ⟨init, ND.can_pure.2 rfl⟩
94
+ | i :: rest, init => by
95
+ obtain ⟨d, hd⟩ := m.options_total act0 ts C i (hview i)
96
+ obtain ⟨ds, hds⟩ := decideAll_total ts C hview rest (fun j => if j = i then d else init j)
97
+ exact ⟨ds, by
98
+ rw [List.foldlM_cons]
99
+ exact ND.can_bind_of (ND.can_bind_of hd (ND.can_pure.2 rfl)) hds⟩
100
+
101
+ /-- A round built from total pieces always has a successor. -/
102
+ theorem total (hts : m.tstates ≠ []) (hc : ∀ c ∈ m.components, c.Total)
103
+ (hview : ∀ ts C i f, ∃ v, ND.Can (m.view ts C i f) v) (C : Config σ) :
104
+ ∃ w a C', (w, (a, C')) ∈ m.roundA act0 C := by
105
+ obtain ⟨ts, hts⟩ := chooseIfMany_total "topology" hts
106
+ obtain ⟨e1, h1⟩ := parallel_total C (m.components.map fun c => c.beforeLook ts) (by
107
+ intro hk hm; simp only [List.mem_map] at hm; obtain ⟨c, hcm, rfl⟩ := hm; exact (hc c hcm).1 ts C)
108
+ obtain ⟨active, ha⟩ := m.activate_total (C.apply e1)
109
+ obtain ⟨ds, hd⟩ := m.decideAll_total act0 ts (C.apply e1) (hview ts _) active
110
+ (fun j => Decision.idle (C.apply e1).agents[j] act0)
111
+ obtain ⟨e2, h2⟩ := parallel_total (C.apply e1) (m.components.map fun c D => c.beforeMove ts D ds) (by
112
+ intro hk hm; simp only [List.mem_map] at hm; obtain ⟨c, hcm, rfl⟩ := hm; exact (hc c hcm).2.1 ts _ ds)
113
+ obtain ⟨e3, h3⟩ := parallel_total (moveAll ts ((C.apply e1).apply e2) ds)
114
+ (m.components.map fun c => c.afterMove ts) (by
115
+ intro hk hm; simp only [List.mem_map] at hm; obtain ⟨c, hcm, rfl⟩ := hm; exact (hc c hcm).2.2 ts _)
116
+ have : ND.Can (m.roundA act0 C) (active, (moveAll ts ((C.apply e1).apply e2) ds).apply e3) :=
117
+ ND.can_bind_of hts (ND.can_bind_of h1 (ND.can_bind_of ha (ND.can_bind_of hd
118
+ (ND.can_bind_of h2 (ND.can_bind_of h3 (ND.can_pure.2 rfl))))))
119
+ obtain ⟨w, hw⟩ := this
120
+ exact ⟨w, _, _, hw⟩
121
+
122
+ /-- A synchronous round with an initial configuration and a successor for every configuration
123
+ has an admissible execution. -/
124
+ theorem hasExecution (init : ND (Config σ)) (hround : m.round = .fsync) (hinit : m.HasInitial init)
125
+ (htot : ∀ C, ∃ w a C', (w, (a, C')) ∈ m.roundA act0 C) : m.HasExecution act0 init := by
126
+ classical
127
+ let next : Config σ → Witness × List (Fin σ.k) × Config σ := fun C =>
128
+ let h := htot C
129
+ (Classical.choose h, Classical.choose (Classical.choose_spec h),
130
+ Classical.choose (Classical.choose_spec (Classical.choose_spec h)))
131
+ let config : Nat → Config σ := fun n => Nat.rec (Classical.choose hinit) (fun _ C => (next C).2.2) n
132
+ refine ⟨⟨config, fun n => (next (config n)).1, fun n => (next (config n)).2.1⟩, ⟨?_, fun n => ?_⟩, ?_⟩
133
+ · exact Classical.choose_spec hinit
134
+ · exact Classical.choose_spec (Classical.choose_spec (Classical.choose_spec (htot (config n))))
135
+ · intro h; exact False.elim (h hround)
136
+
137
+ end Model
138
+ end Simulant
@@ -0,0 +1,130 @@
1
+ /-!
2
+ # Rings, static and dynamic
3
+
4
+ Nodes of a ring of size `N` are `Fin N`; edge `e` joins nodes `e` and
5
+ `e + 1`. A frame is a `Bool` (mirrored or not). The per-round state of a
6
+ dynamic ring is the list of missing edges, in the interpreter's order.
7
+ -/
8
+
9
+ namespace Simulant
10
+
11
+ inductive Move where
12
+ | stay | cw | ccw
13
+ deriving DecidableEq, Repr, Inhabited
14
+
15
+ def Move.flip : Move → Move
16
+ | .cw => .ccw
17
+ | .ccw => .cw
18
+ | .stay => .stay
19
+
20
+ /-- A local move, mapped to a global one through a frame. -/
21
+ def toGlobal (mirrored : Bool) (m : Move) : Move := if mirrored then m.flip else m
22
+
23
+ /-- The per-round state of the topology: the missing edges. -/
24
+ abbrev TState := List Nat
25
+
26
+ namespace Ring
27
+
28
+ variable {N : Nat}
29
+
30
+ def succ (p : Fin N) : Fin N := ⟨(p.val + 1) % N, Nat.mod_lt _ (by have := p.isLt; omega)⟩
31
+ def pred (p : Fin N) : Fin N := ⟨(p.val + N - 1) % N, Nat.mod_lt _ (by have := p.isLt; omega)⟩
32
+
33
+ /-- The neighbour in a global direction, whether or not the edge is present. -/
34
+ def target (p : Fin N) : Move → Fin N
35
+ | .stay => p
36
+ | .cw => succ p
37
+ | .ccw => pred p
38
+
39
+ /-- The node seen at local offset `i` from `p`, in a frame. -/
40
+ def offset (p : Fin N) (mirrored : Bool) (i : Nat) : Fin N :=
41
+ if mirrored then ⟨(p.val + N - i % N) % N, Nat.mod_lt _ (by have := p.isLt; omega)⟩
42
+ else ⟨(p.val + i) % N, Nat.mod_lt _ (by have := p.isLt; omega)⟩
43
+
44
+ /-- The node reached from `p` after `i` steps in a global direction, edges present or not. -/
45
+ def advance (p : Fin N) : Move → Nat → Fin N
46
+ | .stay, _ => p
47
+ | .cw, i => offset p false i
48
+ | .ccw, i => offset p true i
49
+
50
+ /-- Whether moving from `p` in direction `m` uses an edge present in state `ts`. -/
51
+ def canMove (ts : TState) (p : Fin N) : Move → Bool
52
+ | .stay => true
53
+ | .cw => !ts.contains p.val
54
+ | .ccw => !ts.contains ((p.val + N - 1) % N)
55
+
56
+ /-- Sets of at most `k` missing edges, extending `cur` with candidates in order. -/
57
+ def extend (k : Nat) (cur : List Nat) : List Nat → List (List Nat)
58
+ | [] => []
59
+ | e :: rest =>
60
+ let next := cur ++ [e]
61
+ (if next.length ≤ k then next :: extend k next rest else []) ++ extend k cur rest
62
+
63
+ /-- The states of `DynamicRing k`: at most `k` missing edges. -/
64
+ def dynamicStates (N k : Nat) : List TState := [] :: extend k [] (List.range N)
65
+
66
+ /-- The single state of a static ring. -/
67
+ def staticStates : List TState := [[]]
68
+
69
+ /-- Rotation by `r`. -/
70
+ def rot (r : Nat) (p : Fin N) : Fin N := ⟨(p.val + r) % N, Nat.mod_lt _ (by have := p.isLt; omega)⟩
71
+
72
+ end Ring
73
+ end Simulant
74
+
75
+ namespace Simulant.Ring
76
+
77
+ variable {N : Nat}
78
+
79
+ theorem rot_val (r : Nat) (p : Fin N) : (rot r p).val = (p.val + r) % N := rfl
80
+
81
+ theorem rot_rot (a b : Nat) (p : Fin N) : rot a (rot b p) = rot (b + a) p := by
82
+ apply Fin.ext
83
+ simp only [rot_val, Nat.mod_add_mod, Nat.add_assoc]
84
+
85
+ /-- Rotating by a multiple of `N` is the identity. -/
86
+ theorem rot_mul (m : Nat) (p : Fin N) : rot (N * m) p = p := by
87
+ apply Fin.ext
88
+ show (p.val + N * m) % N = p.val
89
+ rw [Nat.add_mul_mod_self_left, Nat.mod_eq_of_lt p.isLt]
90
+
91
+ /-- The inverse of rotation by `r`. -/
92
+ def unrot (r : Nat) (p : Fin N) : Fin N := rot (N - r % N) p
93
+
94
+ theorem unrot_rot (r : Nat) (p : Fin N) : unrot r (rot r p) = p := by
95
+ have hN : 0 < N := Nat.lt_of_le_of_lt (Nat.zero_le _) p.isLt
96
+ have h : r + (N - r % N) = N * (r / N + 1) := by
97
+ have := Nat.mod_add_div r N
98
+ have := Nat.mod_lt r hN
99
+ rw [Nat.mul_add, Nat.mul_one]
100
+ omega
101
+ rw [unrot, rot_rot, h, rot_mul]
102
+
103
+ theorem rot_inj (r : Nat) {p q : Fin N} (h : rot r p = rot r q) : p = q := by
104
+ rw [← unrot_rot r p, h, unrot_rot]
105
+
106
+ @[simp] theorem rot_eq_rot (r : Nat) (p q : Fin N) : rot r p = rot r q ↔ p = q :=
107
+ ⟨rot_inj r, fun h => h ▸ rfl⟩
108
+
109
+ @[simp] theorem rot_beq (r : Nat) (p q : Fin N) : (rot r p == rot r q) = (p == q) := by
110
+ by_cases h : p = q
111
+ · subst h; rw [beq_self_eq_true, beq_self_eq_true]
112
+ · have : rot r p ≠ rot r q := fun e => h (rot_inj r e)
113
+ rw [beq_eq_false_iff_ne.2 this, beq_eq_false_iff_ne.2 h]
114
+
115
+ theorem rot_target (r : Nat) (p : Fin N) (m : Move) : target (rot r p) m = rot r (target p m) := by
116
+ have hN : 0 < N := Nat.lt_of_le_of_lt (Nat.zero_le _) p.isLt
117
+ cases m with
118
+ | stay => rfl
119
+ | cw =>
120
+ apply Fin.ext
121
+ show ((p.val + r) % N + 1) % N = ((p.val + 1) % N + r) % N
122
+ rw [Nat.mod_add_mod, Nat.mod_add_mod, Nat.add_right_comm]
123
+ | ccw =>
124
+ apply Fin.ext
125
+ show ((p.val + r) % N + N - 1) % N = ((p.val + N - 1) % N + r) % N
126
+ have h1 : (p.val + r) % N + N - 1 = (p.val + r) % N + (N - 1) := by omega
127
+ have h2 : p.val + N - 1 = p.val + (N - 1) := by omega
128
+ rw [h1, h2, Nat.mod_add_mod, Nat.mod_add_mod, Nat.add_right_comm]
129
+
130
+ end Simulant.Ring
@@ -0,0 +1,250 @@
1
+ import Simulant.ND
2
+ import Simulant.Ring
3
+
4
+ /-!
5
+ # The standard round (plan, Section 3.7)
6
+
7
+ Everything is generic over a signature `Sig`: the ring size, the number of
8
+ agents, and the composed types of flags, component agent fields, public
9
+ state, memory, actions, world and view. A generated model file instantiates
10
+ it.
11
+
12
+ The round follows the interpreter step by step, so that the interpreter's
13
+ witnesses are witnesses here (see `docs/ir.md`).
14
+ -/
15
+
16
+ namespace Simulant
17
+
18
+ structure Sig where
19
+ N : Nat
20
+ k : Nat
21
+ /-- Flags. -/
22
+ F : Type
23
+ /-- Component agent fields. -/
24
+ X : Type
25
+ /-- Public state of the algorithm. -/
26
+ P : Type
27
+ /-- Memory of the algorithm. -/
28
+ M : Type
29
+ /-- Actions other than moves. -/
30
+ A : Type
31
+ /-- World fields. -/
32
+ W : Type
33
+ /-- What the algorithm sees: one field per sensor it uses. -/
34
+ V : Type
35
+
36
+ inductive Acts where
37
+ | never | adversary | algorithm
38
+ deriving DecidableEq, Repr
39
+
40
+ /-- The flags of a composition, generated as a structure of `Bool`s. -/
41
+ class FlagSet (F : Type) where
42
+ none : F
43
+ merge : F → F → F
44
+ correct : F → Bool
45
+ present : F → Bool
46
+ acts : F → Acts
47
+
48
+ /-- Flags are only ever raised: an agent that never acts never acts again. -/
49
+ class LawfulFlagSet (F : Type) [FlagSet F] : Prop where
50
+ acts_merge_never : ∀ f g : F, FlagSet.acts f = .never → FlagSet.acts (FlagSet.merge f g) = .never
51
+
52
+ variable {σ : Sig}
53
+
54
+ structure Agent (σ : Sig) where
55
+ pos : Fin σ.N
56
+ flags : σ.F
57
+ fields : σ.X
58
+ pub : σ.P
59
+ mem : σ.M
60
+
61
+ structure Config (σ : Sig) where
62
+ agents : Vector (Agent σ) σ.k
63
+ world : σ.W
64
+
65
+ /-- A lens on the part of a state owned by one component. -/
66
+ structure Lens (S T : Type) where
67
+ get : S → T
68
+ set : S → T → S
69
+
70
+ /-- What a hook does: update its component's world fields, and raise flags. -/
71
+ structure Effect (σ : Sig) where
72
+ world : σ.W → σ.W
73
+ raise : Fin σ.k → σ.F
74
+
75
+ def Effect.none [FlagSet σ.F] : Effect σ := ⟨id, fun _ => FlagSet.none⟩
76
+
77
+ /-- Merging the effects of two hooks run on the same snapshot. -/
78
+ def Effect.combine [FlagSet σ.F] (e f : Effect σ) : Effect σ :=
79
+ ⟨f.world ∘ e.world, fun a => FlagSet.merge (e.raise a) (f.raise a)⟩
80
+
81
+ def Config.apply [FlagSet σ.F] (C : Config σ) (e : Effect σ) : Config σ :=
82
+ { agents := Vector.ofFn fun i => { C.agents[i] with flags := FlagSet.merge C.agents[i].flags (e.raise i) },
83
+ world := e.world C.world }
84
+
85
+ structure Decision (σ : Sig) where
86
+ decided : Bool
87
+ move : Move
88
+ pub : σ.P
89
+ mem : σ.M
90
+ act : σ.A
91
+
92
+ /-- What the algorithm returns; the move is local. -/
93
+ structure Out (σ : Sig) where
94
+ move : Move
95
+ pub : σ.P
96
+ mem : σ.M
97
+ act : σ.A
98
+
99
+ structure Component (σ : Sig) where
100
+ beforeLook : TState → Config σ → ND (Effect σ)
101
+ beforeMove : TState → Config σ → (Fin σ.k → Decision σ) → ND (Effect σ)
102
+ afterMove : TState → Config σ → ND (Effect σ)
103
+
104
+ /-! ## Components that own part of the state
105
+
106
+ A component's hooks return an effect on its own part of the world only;
107
+ the composition lifts it through the component's lens. So no component
108
+ can write another's fields, by construction, and the lemma that says so
109
+ (`Hooks.toComponent_preserves`) is proved once for all components. -/
110
+
111
+ /-- An effect on the part of the world a component owns. -/
112
+ structure OwnEffect (σ : Sig) (Wc : Type) where
113
+ world : Wc → Wc
114
+ raise : Fin σ.k → σ.F
115
+
116
+ def OwnEffect.lift (L : Lens σ.W Wc) (e : OwnEffect σ Wc) : Effect σ :=
117
+ ⟨fun w => L.set w (e.world (L.get w)), e.raise⟩
118
+
119
+ /-- The hooks of a component that owns world part `Wc`. -/
120
+ structure Hooks (σ : Sig) (Wc : Type) where
121
+ beforeLook : TState → Config σ → ND (OwnEffect σ Wc)
122
+ beforeMove : TState → Config σ → (Fin σ.k → Decision σ) → ND (OwnEffect σ Wc)
123
+ afterMove : TState → Config σ → ND (OwnEffect σ Wc)
124
+
125
+ def Hooks.toComponent (h : Hooks σ Wc) (L : Lens σ.W Wc) : Component σ where
126
+ beforeLook ts C := do let e ← h.beforeLook ts C; pure (e.lift L)
127
+ beforeMove ts C ds := do let e ← h.beforeMove ts C ds; pure (e.lift L)
128
+ afterMove ts C := do let e ← h.afterMove ts C; pure (e.lift L)
129
+
130
+ /-- The lens of a component that owns no world field. -/
131
+ def Lens.unit (S : Type) : Lens S Unit := ⟨fun _ => (), fun s _ => s⟩
132
+
133
+ /-- A lens whose `get` reads back what `set` wrote. -/
134
+ class LawfulLens (L : Lens S T) : Prop where
135
+ get_set : ∀ s t, L.get (L.set s t) = t
136
+
137
+ inductive FrameModel (σ : Sig) where
138
+ | common
139
+ | dynamic
140
+ | persistent (get : σ.X → Bool)
141
+
142
+ inductive RoundKind where
143
+ | fsync | ssync | sequential
144
+ deriving DecidableEq, Repr
145
+
146
+ structure Model (σ : Sig) where
147
+ tstates : List TState
148
+ components : List (Component σ)
149
+ frame : FrameModel σ
150
+ round : RoundKind
151
+ /-- The sensors of an activation: configuration, agent, frame. -/
152
+ view : TState → Config σ → Fin σ.k → Bool → ND σ.V
153
+ step : σ.M → σ.P → σ.V → Out σ
154
+
155
+ /-- All hooks of a hook point read the same snapshot; their effects are merged. -/
156
+ def parallel [FlagSet σ.F] : List (Config σ → ND (Effect σ)) → Config σ → ND (Effect σ)
157
+ | [], _ => pure Effect.none
158
+ | h :: hs, C => do
159
+ let e ← h C
160
+ let r ← parallel hs C
161
+ pure (e.combine r)
162
+
163
+ /-- The decision of an agent that does not decide: stay, keep its state. -/
164
+ def Decision.idle (a : Agent σ) (act : σ.A) : Decision σ :=
165
+ { decided := false, move := .stay, pub := a.pub, mem := a.mem, act }
166
+
167
+ /-- Rigid movement along the edges present; only agents that still act apply their decision. -/
168
+ def moveAll [FlagSet σ.F] (ts : TState) (C : Config σ) (ds : Fin σ.k → Decision σ) : Config σ :=
169
+ { C with agents := Vector.ofFn fun i =>
170
+ let a := C.agents[i]
171
+ let d := ds i
172
+ if d.decided && FlagSet.acts a.flags != .never then
173
+ { a with
174
+ pos := if d.move != .stay && Ring.canMove ts a.pos d.move then Ring.target a.pos d.move else a.pos,
175
+ pub := d.pub, mem := d.mem }
176
+ else a }
177
+
178
+ /-- Sub-lists of `l` with `n` elements, in lexicographic order of positions. -/
179
+ def combos : Nat → List α → List (List α)
180
+ | 0, _ => [[]]
181
+ | _ + 1, [] => []
182
+ | n + 1, x :: xs => (combos n xs).map (x :: ·) ++ combos (n + 1) xs
183
+
184
+ /-- Nonempty sub-lists, smallest first. -/
185
+ def nonemptySubsets (l : List α) : List (List α) :=
186
+ (List.range l.length).flatMap fun s => combos (s + 1) l
187
+
188
+ def agentName (i : Fin k) : String := "a" ++ toString i.val
189
+
190
+ /-- A single option is no choice: it leaves no witness. -/
191
+ def chooseIfMany (site : String) : List α → ND α
192
+ | [x] => pure x
193
+ | xs => ND.choose site xs
194
+
195
+ namespace Model
196
+
197
+ variable [FlagSet σ.F] (m : Model σ)
198
+
199
+ /-- FSYNC: all eligible agents; SSYNC: a nonempty subset; sequential: a singleton. -/
200
+ def activate (C : Config σ) : ND (List (Fin σ.k)) :=
201
+ let eligible := (List.finRange σ.k).filter fun i => FlagSet.acts C.agents[i].flags != .never
202
+ match m.round with
203
+ | .fsync => pure eligible
204
+ | .ssync => if eligible.isEmpty then pure eligible else ND.choose "activation" (nonemptySubsets eligible)
205
+ | .sequential => if eligible.isEmpty then pure eligible else ND.choose "activation" (eligible.map ([·]))
206
+
207
+ def frames (a : Agent σ) : List Bool :=
208
+ match m.frame with
209
+ | .common => [false]
210
+ | .dynamic => [false, true]
211
+ | .persistent get => [get a.fields]
212
+
213
+ /-- The decisions one agent may take. -/
214
+ def options (act0 : σ.A) (ts : TState) (C : Config σ) (i : Fin σ.k) : ND (Decision σ) :=
215
+ let a := C.agents[i]
216
+ match FlagSet.acts a.flags with
217
+ | .adversary => do
218
+ let mv ← ND.choose ("adversary " ++ agentName i) [Move.stay, .cw, .ccw]
219
+ pure { Decision.idle a act0 with decided := true, move := mv }
220
+ | _ => do
221
+ let f ← chooseIfMany ("frame " ++ agentName i) (m.frames a)
222
+ let v ← m.view ts C i f
223
+ let o := m.step a.mem a.pub v
224
+ pure { decided := true, move := toGlobal f o.move, pub := o.pub, mem := o.mem, act := o.act }
225
+
226
+ /-- The product, over the activated agents, of their decisions. -/
227
+ def decideAll (act0 : σ.A) (ts : TState) (C : Config σ) (active : List (Fin σ.k)) : ND (Fin σ.k → Decision σ) :=
228
+ active.foldlM (fun ds i => do
229
+ let d ← m.options act0 ts C i
230
+ pure fun j => if j = i then d else ds j)
231
+ (fun j => Decision.idle C.agents[j] act0)
232
+
233
+ /-- One round, with the activated agents. `act0` is the default value of the actions. -/
234
+ def roundA (act0 : σ.A) (C : Config σ) : ND (List (Fin σ.k) × Config σ) := do
235
+ let ts ← chooseIfMany "topology" m.tstates
236
+ let e ← parallel (m.components.map fun c => c.beforeLook ts) C
237
+ let C := C.apply e
238
+ let active ← m.activate C
239
+ let ds ← m.decideAll act0 ts C active
240
+ let e ← parallel (m.components.map fun c D => c.beforeMove ts D ds) C
241
+ let C := moveAll ts (C.apply e) ds
242
+ let e ← parallel (m.components.map fun c => c.afterMove ts) C
243
+ pure (active, C.apply e)
244
+
245
+ /-- The step relation, with witnesses. -/
246
+ def Step (act0 : σ.A) (C : Config σ) (w : Witness) (C' : Config σ) : Prop :=
247
+ ∃ active, (w, (active, C')) ∈ m.roundA act0 C
248
+
249
+ end Model
250
+ end Simulant
package/Simulant.lean ADDED
@@ -0,0 +1,9 @@
1
+ import Simulant.ND
2
+ import Simulant.Ring
3
+ import Simulant.Round
4
+ import Simulant.Builtins
5
+ import Simulant.Exec
6
+ import Simulant.Lemmas
7
+ import Simulant.Equivariance
8
+ import Simulant.NonVacuity
9
+ import Simulant.Invariants
package/lakefile.toml ADDED
@@ -0,0 +1,13 @@
1
+ name = "simulant"
2
+ version = "0.1.0"
3
+ defaultTargets = ["Simulant"]
4
+
5
+ # The semantic core and the component library (plan, Section 5.1).
6
+ [[lean_lib]]
7
+ name = "Simulant"
8
+ globs = ["Simulant", "Simulant.+"]
9
+
10
+ # Generated model files and their proofs, for the Phase 2 prototype.
11
+ [[lean_lib]]
12
+ name = "Examples"
13
+ globs = ["Examples.+"]
package/lean-toolchain ADDED
@@ -0,0 +1 @@
1
+ leanprover/lean4:v4.35.0