@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.
- package/LICENCE.txt +515 -0
- package/Simulant/Builtins.lean +24 -0
- package/Simulant/Equivariance.lean +149 -0
- package/Simulant/Exec.lean +150 -0
- package/Simulant/Invariants.lean +89 -0
- package/Simulant/Lemmas.lean +146 -0
- package/Simulant/Library/BlackHole.lean +54 -0
- package/Simulant/Library/CrashAtNode.lean +46 -0
- package/Simulant/Library/CrashStop.lean +38 -0
- package/Simulant/Library/Pebbles.lean +51 -0
- package/Simulant/Library/PersistentReflection.lean +40 -0
- package/Simulant/Library/Visited.lean +41 -0
- package/Simulant/Library.lean +6 -0
- package/Simulant/LibraryLemmas/BlackHole.lean +52 -0
- package/Simulant/ND.lean +88 -0
- package/Simulant/NonVacuity.lean +138 -0
- package/Simulant/Ring.lean +130 -0
- package/Simulant/Round.lean +250 -0
- package/Simulant.lean +9 -0
- package/lakefile.toml +13 -0
- package/lean-toolchain +1 -0
- package/package.json +23 -3
- package/README.md +0 -3
|
@@ -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
|
package/Simulant/ND.lean
ADDED
|
@@ -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
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
|