Skip to main content

automation_structures/primitives/
propagation_pass.rs

1// Executable PropagationPassGraph contract. The graph is
2// immutable, each round snapshots the values, UpdateNode commits one local
3// combine from that snapshot, and EndRound alone charges the iteration and
4// records whether the round changed anything.
5//
6// Edges are directed (source, target) pairs. The concrete domain combine is
7// the TLA+ miniature: if any in-neighbour has a smaller snapshot value, the
8// target decreases by one; otherwise it retains its snapshot value.
9
10use vstd::prelude::*;
11
12verus! {
13
14#[derive(Clone, Copy, PartialEq, Eq, Debug)]
15pub enum Round {
16    Idle,
17    Running,
18}
19
20pub open spec fn has_better_in_neighbor(
21    edges: Seq<(usize, usize)>,
22    snapshot: Seq<u64>,
23    n: usize,
24) -> bool {
25    exists|i: int| 0 <= i < edges.len()
26        && edges[i].1 == n
27        && snapshot[edges[i].0 as int] < snapshot[n as int]
28}
29
30pub open spec fn local_combine(
31    edges: Seq<(usize, usize)>,
32    snapshot: Seq<u64>,
33    n: usize,
34) -> int {
35    if has_better_in_neighbor(edges, snapshot, n) {
36        snapshot[n as int] as int - 1
37    } else {
38        snapshot[n as int] as int
39    }
40}
41
42pub struct PropagationPass {
43    pub num_nodes: usize,
44    pub max_iterations: u64,
45    pub max_value: u64,
46    pub edges: Vec<(usize, usize)>,
47    pub values: Vec<u64>,
48    pub snapshot: Vec<u64>,
49    pub updated: Vec<bool>,
50    pub iteration: u64,
51    pub changed: bool,
52    pub round: Round,
53}
54
55impl PropagationPass {
56    // -- Specifications --------------------------------------------------
57
58    pub open spec fn type_invariant(&self) -> bool {
59        &&& self.values.len() == self.num_nodes
60        &&& self.snapshot.len() == self.num_nodes
61        &&& self.updated.len() == self.num_nodes
62        &&& (forall|i: int| 0 <= i < self.values.len()
63                ==> #[trigger] self.values@[i] <= self.max_value)
64        &&& (forall|i: int| 0 <= i < self.snapshot.len()
65                ==> #[trigger] self.snapshot@[i] <= self.max_value)
66        &&& (forall|i: int| 0 <= i < self.edges.len()
67                ==> #[trigger] self.edges@[i].0 < self.num_nodes
68                    && self.edges@[i].1 < self.num_nodes)
69    }
70
71    pub open spec fn iteration_bound(&self) -> bool {
72        self.iteration <= self.max_iterations
73    }
74
75    pub open spec fn round_bound(&self) -> bool {
76        self.round == Round::Running ==> self.iteration < self.max_iterations
77    }
78
79    pub open spec fn running_changed(&self) -> bool {
80        self.round == Round::Running ==> self.changed
81    }
82
83    pub open spec fn all_updated(&self) -> bool {
84        forall|i: int| 0 <= i < self.updated.len() ==> #[trigger] self.updated@[i]
85    }
86
87    /// TLA+ `SnapshotLocality`: every node committed in this round is the local
88    /// combine of the shared round-start snapshot.
89    pub open spec fn snapshot_locality(&self) -> bool {
90        forall|i: int| 0 <= i < self.updated.len() && #[trigger] self.updated@[i]
91            ==> self.values@[i] as int == local_combine(self.edges@, self.snapshot@, i as usize)
92    }
93
94    pub open spec fn settled_ok(&self) -> bool {
95        !self.changed ==> self.values@ == self.snapshot@ && self.all_updated()
96    }
97
98    pub open spec fn inv(&self) -> bool {
99        &&& self.type_invariant()
100        &&& self.iteration_bound()
101        &&& self.round_bound()
102        &&& self.running_changed()
103        &&& self.settled_ok()
104        &&& self.snapshot_locality()
105    }
106
107    pub open spec fn settled_or_iteration_limit(&self) -> bool {
108        !self.changed || self.iteration == self.max_iterations
109    }
110
111    // -- Init ------------------------------------------------------------
112
113    pub fn new(
114        num_nodes: usize,
115        max_iterations: u64,
116        max_value: u64,
117        edges: Vec<(usize, usize)>,
118        init_values: Vec<u64>,
119    ) -> (p: PropagationPass)
120        requires
121            init_values.len() == num_nodes,
122            forall|i: int| 0 <= i < init_values.len() ==> init_values@[i] <= max_value,
123            forall|i: int| 0 <= i < edges.len()
124                ==> edges@[i].0 < num_nodes && edges@[i].1 < num_nodes,
125        ensures
126            p.num_nodes == num_nodes,
127            p.max_iterations == max_iterations,
128            p.max_value == max_value,
129            p.edges@ == edges@,
130            p.values@ == init_values@,
131            p.snapshot@ == init_values@,
132            p.iteration == 0,
133            p.changed,
134            p.round == Round::Idle,
135            forall|i: int| 0 <= i < p.updated.len() ==> !p.updated@[i],
136            p.inv(),
137    {
138        let snapshot = clone_values(&init_values);
139        let updated = false_vector(num_nodes);
140        PropagationPass {
141            num_nodes,
142            max_iterations,
143            max_value,
144            edges,
145            values: init_values,
146            snapshot,
147            updated,
148            iteration: 0,
149            changed: true,
150            round: Round::Idle,
151        }
152    }
153
154    // -- Executable queries ---------------------------------------------
155
156    pub fn all_nodes_updated(&self) -> (b: bool)
157        requires self.type_invariant(),
158        ensures b == self.all_updated(),
159    {
160        let n = self.updated.len();
161        let mut i: usize = 0;
162        while i < n
163            invariant
164                i <= n,
165                n == self.updated.len(),
166                forall|k: int| 0 <= k < i ==> self.updated@[k],
167            decreases n - i,
168        {
169            if !self.updated[i] {
170                assert(!self.all_updated());
171                return false;
172            }
173            i = i + 1;
174        }
175        assert(self.all_updated());
176        true
177    }
178
179    /// Execute the local combine using only immutable `edges` and `snapshot`.
180    pub fn combine_node(&self, n: usize) -> (value: u64)
181        requires
182            self.type_invariant(),
183            n < self.num_nodes,
184        ensures
185            value as int == local_combine(self.edges@, self.snapshot@, n),
186            value <= self.max_value,
187    {
188        let edge_count = self.edges.len();
189        let mut i: usize = 0;
190        while i < edge_count
191            invariant
192                i <= edge_count,
193                edge_count == self.edges.len(),
194                self.type_invariant(),
195                n < self.num_nodes,
196                forall|j: int| 0 <= j < i ==> !(
197                    self.edges@[j].1 == n
198                    && self.snapshot@[self.edges@[j].0 as int] < self.snapshot@[n as int]
199                ),
200            decreases edge_count - i,
201        {
202            let edge = self.edges[i];
203            if edge.1 == n && self.snapshot[edge.0] < self.snapshot[n] {
204                assert(has_better_in_neighbor(self.edges@, self.snapshot@, n));
205                assert(self.snapshot@[n as int] > 0);
206                return self.snapshot[n] - 1;
207            }
208            i = i + 1;
209        }
210        assert(!has_better_in_neighbor(self.edges@, self.snapshot@, n));
211        self.snapshot[n]
212    }
213
214    // -- Round actions ---------------------------------------------------
215
216    /// TLA+ `StartRound`: capture the common snapshot and clear the update set.
217    pub fn start_round(&mut self)
218        requires
219            old(self).inv(),
220            old(self).round == Round::Idle,
221            old(self).changed,
222            old(self).iteration < old(self).max_iterations,
223        ensures
224            final(self).num_nodes == old(self).num_nodes,
225            final(self).max_iterations == old(self).max_iterations,
226            final(self).max_value == old(self).max_value,
227            final(self).edges@ == old(self).edges@,
228            final(self).values@ == old(self).values@,
229            final(self).snapshot@ == old(self).values@,
230            forall|i: int| 0 <= i < final(self).updated.len() ==> !final(self).updated@[i],
231            final(self).iteration == old(self).iteration,
232            final(self).changed == old(self).changed,
233            final(self).round == Round::Running,
234            final(self).inv(),
235    {
236        self.snapshot = clone_values(&self.values);
237        self.updated = false_vector(self.num_nodes);
238        self.round = Round::Running;
239    }
240
241    /// TLA+ `UpdateNode(n)`: compute from the round snapshot and commit one node.
242    pub fn update_node(&mut self, n: usize)
243        requires
244            old(self).inv(),
245            old(self).round == Round::Running,
246            n < old(self).num_nodes,
247            !old(self).updated@[n as int],
248        ensures
249            final(self).num_nodes == old(self).num_nodes,
250            final(self).max_iterations == old(self).max_iterations,
251            final(self).max_value == old(self).max_value,
252            final(self).edges@ == old(self).edges@,
253            final(self).snapshot@ == old(self).snapshot@,
254            final(self).values@ == old(self).values@.update(
255                n as int,
256                local_combine(old(self).edges@, old(self).snapshot@, n) as u64,
257            ),
258            final(self).updated@ == old(self).updated@.update(n as int, true),
259            final(self).iteration == old(self).iteration,
260            final(self).changed == old(self).changed,
261            final(self).round == old(self).round,
262            final(self).inv(),
263    {
264        let next = self.combine_node(n);
265        self.values.set(n, next);
266        self.updated.set(n, true);
267        assert(self.snapshot_locality()) by {
268            assert forall|i: int| 0 <= i < self.updated.len() && self.updated@[i]
269                implies self.values@[i] as int
270                    == local_combine(self.edges@, self.snapshot@, i as usize) by {
271                if i == n as int {
272                    assert(self.values@[i] == next);
273                } else {
274                    assert(self.values@[i] == old(self).values@[i]);
275                    assert(self.updated@[i] == old(self).updated@[i]);
276                }
277            }
278        }
279    }
280
281    /// TLA+ `EndRound`: require full coverage, detect movement, and charge once.
282    pub fn end_round(&mut self)
283        requires
284            old(self).inv(),
285            old(self).round == Round::Running,
286            old(self).all_updated(),
287        ensures
288            final(self).num_nodes == old(self).num_nodes,
289            final(self).max_iterations == old(self).max_iterations,
290            final(self).max_value == old(self).max_value,
291            final(self).edges@ == old(self).edges@,
292            final(self).values@ == old(self).values@,
293            final(self).snapshot@ == old(self).snapshot@,
294            final(self).updated@ == old(self).updated@,
295            crate::connectives::counter::increment(
296                old(self).iteration as int,
297                final(self).iteration as int,
298            ),
299            final(self).changed == (old(self).values@ != old(self).snapshot@),
300            final(self).round == Round::Idle,
301            final(self).inv(),
302    {
303        let differ = !vectors_equal(&self.values, &self.snapshot);
304        self.changed = differ;
305        self.iteration = self.iteration + 1;
306        self.round = Round::Idle;
307    }
308
309    /// TLA+ `Terminate`: an idle self-loop at a fixed point or the ceiling.
310    pub fn terminate(&mut self)
311        requires
312            old(self).inv(),
313            old(self).round == Round::Idle,
314            old(self).settled_or_iteration_limit(),
315        ensures
316            final(self).num_nodes == old(self).num_nodes,
317            final(self).max_iterations == old(self).max_iterations,
318            final(self).max_value == old(self).max_value,
319            final(self).edges@ == old(self).edges@,
320            final(self).values@ == old(self).values@,
321            final(self).snapshot@ == old(self).snapshot@,
322            final(self).updated@ == old(self).updated@,
323            final(self).iteration == old(self).iteration,
324            final(self).changed == old(self).changed,
325            final(self).round == old(self).round,
326            final(self).inv(),
327    {
328    }
329}
330
331fn false_vector(n: usize) -> (out: Vec<bool>)
332    ensures
333        out.len() == n,
334        forall|i: int| 0 <= i < out.len() ==> !out@[i],
335{
336    let mut out: Vec<bool> = Vec::new();
337    let mut i: usize = 0;
338    while i < n
339        invariant
340            i <= n,
341            out.len() == i,
342            forall|k: int| 0 <= k < i ==> !out@[k],
343        decreases n - i,
344    {
345        out.push(false);
346        i = i + 1;
347    }
348    out
349}
350
351fn clone_values(v: &Vec<u64>) -> (out: Vec<u64>)
352    ensures out@ == v@,
353{
354    let mut out: Vec<u64> = Vec::new();
355    let n = v.len();
356    let mut i: usize = 0;
357    while i < n
358        invariant
359            i <= n,
360            n == v.len(),
361            out.len() == i,
362            forall|k: int| 0 <= k < i ==> out@[k] == v@[k],
363        decreases n - i,
364    {
365        out.push(v[i]);
366        i = i + 1;
367    }
368    assert(out@ =~= v@);
369    out
370}
371
372fn vectors_equal(a: &Vec<u64>, b: &Vec<u64>) -> (same: bool)
373    requires a.len() == b.len(),
374    ensures same == (a@ == b@),
375{
376    let n = a.len();
377    let mut i: usize = 0;
378    while i < n
379        invariant
380            i <= n,
381            n == a.len(),
382            a.len() == b.len(),
383            forall|k: int| 0 <= k < i ==> a@[k] == b@[k],
384        decreases n - i,
385    {
386        if a[i] != b[i] {
387            assert(a@[i as int] != b@[i as int]);
388            return false;
389        }
390        i = i + 1;
391    }
392    assert(a@ =~= b@);
393    true
394}
395
396}