Skip to main content

automation_structures/primitives/
backtracking_traversal.rs

1// Executable modulo-3 carrier for BacktrackingTraversalUndo.tla, bound by
2// BacktrackingTraversalUndo.cfg. A descent records the pre-mutation value and
3// chosen delta in an undo token, then applies Mutate in the same commit. An
4// ascent applies the recorded inverse and pops both path and ledger. The model
5// remains order-parametric: Completeness is leaf soundness, not eventual
6// coverage of every leaf.
7
8use vstd::prelude::*;
9
10verus! {
11
12/// Reversible auxiliary-state mutation retained for one descent.
13pub struct UndoToken {
14    /// Auxiliary value before the descent.
15    pub saved: u64,
16    /// Mutation delta applied by the descent.
17    pub delta: u64,
18}
19
20/// Reversible depth-first traversal owner.
21pub struct BacktrackingTraversal {
22    /// Number of admitted choices at each non-leaf depth.
23    pub branch_factor: u64,
24    /// Required depth of a complete leaf path.
25    pub max_depth: usize,
26    /// Auxiliary value at the root.
27    pub init_aux: u64,
28    /// Current branch-choice path.
29    pub path: Vec<u64>,
30    /// Current auxiliary value.
31    pub aux: u64,
32    /// Undo tokens aligned with the current path.
33    pub ledger: Vec<UndoToken>,
34    /// A Vec is the executable representation of the TLA+ visited set;
35    /// `visited_unique` and Visit's freshness guard preserve set semantics.
36    pub visited: Vec<Vec<u64>>,
37}
38
39impl BacktrackingTraversal {
40    /// Apply the modulo-three mutation used by the traversal profile.
41    pub open spec fn mutate_spec(v: u64, d: u64) -> int {
42        ((v as int) + (d as int)) % 3
43    }
44
45    /// Apply the inverse modulo-three mutation used during restoration.
46    pub open spec fn undo_spec(v: u64, d: u64) -> int {
47        ((v as int) + (3 - d as int)) % 3
48    }
49
50    /// Whether path, ledger, auxiliary value, and choices have valid shape and bounds.
51    pub open spec fn type_invariant(&self) -> bool {
52        &&& self.init_aux < 3
53        &&& self.aux < 3
54        &&& self.path.len() <= self.max_depth
55        &&& (forall|i: int| 0 <= i < self.path.len()
56                ==> 1 <= #[trigger] self.path@[i] <= self.branch_factor)
57        &&& (forall|i: int| 0 <= i < self.ledger.len() ==> {
58                &&& #[trigger] self.ledger@[i].saved < 3
59                &&& 1 <= self.ledger@[i].delta <= 2
60            })
61    }
62
63    /// Pairing: one token per outstanding descent and the live value is
64    /// exactly the mutation named by the head token.
65    pub open spec fn pairing(&self) -> bool {
66        &&& self.ledger.len() == self.path.len()
67        &&& (self.path.len() == 0 ==> self.aux == self.init_aux)
68        &&& (self.path.len() > 0 ==> self.aux as int
69            == Self::mutate_spec(
70                self.ledger@[self.path.len() - 1].saved,
71                self.ledger@[self.path.len() - 1].delta))
72    }
73
74    /// StateRestoration: the ledger is a checkpoint chain, not a depth-derived
75    /// counter sequence.
76    pub open spec fn state_restoration(&self) -> bool {
77        &&& (self.ledger.len() >= 1 ==> self.ledger@[0].saved == self.init_aux)
78        &&& (forall|i: int| 1 <= i < self.ledger.len() ==>
79            #[trigger] self.ledger@[i].saved as int
80                == Self::mutate_spec(self.ledger@[i - 1].saved, self.ledger@[i - 1].delta))
81    }
82
83    /// Completeness: every recorded visit is a valid full-depth leaf.
84    pub open spec fn completeness(&self) -> bool {
85        forall|e: int| 0 <= e < self.visited.len() ==> {
86            &&& #[trigger] self.visited@[e].len() == self.max_depth
87            &&& (forall|j: int| 0 <= j < self.max_depth as int
88                ==> 1 <= #[trigger] self.visited@[e]@[j] <= self.branch_factor)
89        }
90    }
91
92    /// Whether no full-depth path is recorded more than once.
93    pub open spec fn visited_unique(&self) -> bool {
94        forall|i: int, j: int|
95            0 <= i < self.visited.len() && 0 <= j < self.visited.len() && i != j
96                ==> #[trigger] self.visited@[i]@ != #[trigger] self.visited@[j]@
97    }
98
99    /// Whether the completed-path set contains `p`.
100    pub open spec fn visited_contains(&self, p: Seq<u64>) -> bool {
101        exists|e: int| 0 <= e < self.visited.len() && #[trigger] self.visited@[e]@ == p
102    }
103
104    /// Whether all traversal and restoration obligations hold.
105    pub open spec fn inv(&self) -> bool {
106        self.type_invariant()
107            && self.pairing()
108            && self.state_restoration()
109            && self.completeness()
110            && self.visited_unique()
111    }
112
113    /// Whether the current path has reached the configured depth.
114    pub open spec fn is_leaf(&self) -> bool {
115        self.path.len() == self.max_depth
116    }
117
118    /// Apply the modulo-three auxiliary mutation.
119    pub fn mutate_exec(v: u64, d: u64) -> (out: u64)
120        requires v < 3, 1 <= d <= 2,
121        ensures out < 3, out as int == Self::mutate_spec(v, d),
122    {
123        if d == 1 {
124            if v == 2 { 0 } else { v + 1 }
125        } else {
126            if v == 0 { 2 } else { v - 1 }
127        }
128    }
129
130    /// Apply the inverse modulo-three auxiliary mutation.
131    pub fn undo_exec(v: u64, d: u64) -> (out: u64)
132        requires v < 3, 1 <= d <= 2,
133        ensures out < 3, out as int == Self::undo_spec(v, d),
134    {
135        if d == 1 {
136            if v == 0 { 2 } else { v - 1 }
137        } else {
138            if v == 2 { 0 } else { v + 1 }
139        }
140    }
141
142    /// Prove that the undo operation reverses an admitted mutation.
143    pub proof fn lemma_undo_inverts(v: u64, d: u64)
144        requires v < 3, 1 <= d <= 2,
145        ensures Self::undo_spec(Self::mutate_spec(v, d) as u64, d) == v as int,
146    {
147        if v == 0 {
148            if d == 1 { assert(Self::mutate_spec(v, d) == 1); }
149            else { assert(Self::mutate_spec(v, d) == 2); }
150        } else if v == 1 {
151            if d == 1 { assert(Self::mutate_spec(v, d) == 2); }
152            else { assert(Self::mutate_spec(v, d) == 0); }
153        } else {
154            assert(v == 2);
155            if d == 1 { assert(Self::mutate_spec(v, d) == 0); }
156            else { assert(Self::mutate_spec(v, d) == 1); }
157        }
158    }
159
160    /// Construct a traversal at its root with an empty visited set.
161    pub fn new(branch_factor: u64, max_depth: usize, init_aux: u64) -> (t: BacktrackingTraversal)
162        requires init_aux < 3,
163        ensures
164            t.branch_factor == branch_factor,
165            t.max_depth == max_depth,
166            t.init_aux == init_aux,
167            t.path@.len() == 0,
168            t.ledger@.len() == 0,
169            t.aux == init_aux,
170            t.visited@.len() == 0,
171            t.inv(),
172    {
173        BacktrackingTraversal {
174            branch_factor,
175            max_depth,
176            init_aux,
177            path: Vec::new(),
178            aux: init_aux,
179            ledger: Vec::new(),
180            visited: Vec::new(),
181        }
182    }
183
184    /// Whether the current path has reached the configured leaf depth.
185    pub fn is_leaf_exec(&self) -> (b: bool)
186        ensures b == self.is_leaf(),
187    {
188        self.path.len() == self.max_depth
189    }
190
191    /// Whether a choice and mutation delta enable another descent.
192    pub fn can_descend(&self, c: u64, d: u64) -> (b: bool)
193        requires self.type_invariant(),
194        ensures b == (!self.is_leaf() && 1 <= c <= self.branch_factor && 1 <= d <= 2),
195    {
196        self.path.len() < self.max_depth
197            && 1 <= c && c <= self.branch_factor
198            && 1 <= d && d <= 2
199    }
200
201    /// Whether an undo token is available for ascent.
202    pub fn can_ascend(&self) -> (b: bool)
203        ensures b == (self.path.len() >= 1),
204    {
205        self.path.len() >= 1
206    }
207
208    /// Whether a path occurs in the visited-leaf ledger.
209    pub fn has_visited(&self, p: &Vec<u64>) -> (b: bool)
210        ensures b == self.visited_contains(p@),
211    {
212        let len = self.visited.len();
213        let mut i: usize = 0;
214        while i < len
215            invariant
216                i <= len,
217                len == self.visited.len(),
218                forall|e: int| 0 <= e < i ==> #[trigger] self.visited@[e]@ != p@,
219            decreases len - i,
220        {
221            if paths_equal(&self.visited[i], p) {
222                assert(self.visited@[i as int]@ == p@);
223                return true;
224            }
225            i = i + 1;
226        }
227        false
228    }
229
230    /// Whether the current path is a fresh leaf that may be visited.
231    pub fn can_visit(&self) -> (b: bool)
232        ensures b == (self.is_leaf() && !self.visited_contains(self.path@)),
233    {
234        self.is_leaf_exec() && !self.has_visited(&self.path)
235    }
236
237    /// `Descend(c,d)`: record the undo token and apply its mutation atomically.
238    pub fn descend(&mut self, c: u64, d: u64)
239        requires
240            old(self).inv(),
241            !old(self).is_leaf(),
242            1 <= c <= old(self).branch_factor,
243            1 <= d <= 2,
244        ensures
245            final(self).branch_factor == old(self).branch_factor,
246            final(self).max_depth == old(self).max_depth,
247            final(self).init_aux == old(self).init_aux,
248            final(self).path@ == old(self).path@.push(c),
249            final(self).ledger@.len() == old(self).ledger@.len() + 1,
250            final(self).ledger@[old(self).ledger@.len() as int].saved == old(self).aux,
251            final(self).ledger@[old(self).ledger@.len() as int].delta == d,
252            forall|i: int| 0 <= i < old(self).ledger@.len() ==>
253                #[trigger] final(self).ledger@[i].saved == old(self).ledger@[i].saved
254                && final(self).ledger@[i].delta == old(self).ledger@[i].delta,
255            final(self).aux as int == Self::mutate_spec(old(self).aux, d),
256            final(self).visited@ == old(self).visited@,
257            final(self).inv(),
258    {
259        let next = Self::mutate_exec(self.aux, d);
260        let token = UndoToken { saved: self.aux, delta: d };
261        self.ledger.push(token);
262        self.path.push(c);
263        self.aux = next;
264    }
265
266    /// `Visit`: append the current leaf only when it is fresh.
267    pub fn visit(&mut self)
268        requires
269            old(self).inv(),
270            old(self).is_leaf(),
271            !old(self).visited_contains(old(self).path@),
272        ensures
273            final(self).branch_factor == old(self).branch_factor,
274            final(self).max_depth == old(self).max_depth,
275            final(self).init_aux == old(self).init_aux,
276            final(self).path@ == old(self).path@,
277            final(self).ledger@ == old(self).ledger@,
278            final(self).aux == old(self).aux,
279            final(self).visited@.len() == old(self).visited@.len() + 1,
280            final(self).visited@[old(self).visited@.len() as int]@ == old(self).path@,
281            forall|i: int| 0 <= i < old(self).visited@.len()
282                ==> #[trigger] final(self).visited@[i]@ == old(self).visited@[i]@,
283            final(self).inv(),
284    {
285        let copy = clone_path(&self.path);
286        self.visited.push(copy);
287        assert(self.completeness()) by {
288            assert forall|e: int| 0 <= e < self.visited.len() implies {
289                &&& #[trigger] self.visited@[e].len() == self.max_depth
290                &&& (forall|j: int| 0 <= j < self.max_depth as int
291                    ==> 1 <= #[trigger] self.visited@[e]@[j] <= self.branch_factor)
292            } by {
293                if e < old(self).visited.len() {
294                } else {
295                    assert(e == old(self).visited.len());
296                    assert(self.visited@[e]@ == old(self).path@);
297                    assert forall|j: int| 0 <= j < self.max_depth as int
298                        implies 1 <= #[trigger] self.visited@[e]@[j] <= self.branch_factor by {
299                        assert(self.visited@[e]@[j] == old(self).path@[j]);
300                    }
301                }
302            }
303        }
304        assert(self.visited_unique()) by {
305            assert forall|i: int, j: int|
306                0 <= i < self.visited.len() && 0 <= j < self.visited.len() && i != j
307                    implies #[trigger] self.visited@[i]@ != #[trigger] self.visited@[j]@ by {
308                if i < old(self).visited.len() && j < old(self).visited.len() {
309                } else if i == old(self).visited.len() {
310                    assert(self.visited@[i]@ == old(self).path@);
311                    assert(self.visited@[j]@ == old(self).visited@[j]@);
312                } else {
313                    assert(j == old(self).visited.len());
314                    assert(self.visited@[j]@ == old(self).path@);
315                    assert(self.visited@[i]@ == old(self).visited@[i]@);
316                }
317            }
318        }
319    }
320
321    /// `Ascend`: apply the recorded inverse, then pop token and path.
322    pub fn ascend(&mut self)
323        requires old(self).inv(), old(self).path.len() >= 1,
324        ensures
325            final(self).branch_factor == old(self).branch_factor,
326            final(self).max_depth == old(self).max_depth,
327            final(self).init_aux == old(self).init_aux,
328            final(self).path@ == old(self).path@.drop_last(),
329            final(self).ledger@ == old(self).ledger@.drop_last(),
330            final(self).aux as int == Self::undo_spec(
331                old(self).aux,
332                old(self).ledger@[old(self).ledger@.len() - 1].delta),
333            final(self).aux == old(self).ledger@[old(self).ledger@.len() - 1].saved,
334            final(self).visited@ == old(self).visited@,
335            final(self).inv(),
336    {
337        let depth = self.path.len();
338        let saved = self.ledger[depth - 1].saved;
339        let _ = saved;
340        let delta = self.ledger[depth - 1].delta;
341        assert(self.aux as int == Self::mutate_spec(saved, delta));
342        proof { Self::lemma_undo_inverts(saved, delta); }
343        let restored = Self::undo_exec(self.aux, delta);
344        assert(restored == saved);
345        self.aux = restored;
346        self.ledger.pop();
347        self.path.pop();
348        proof {
349            assert(self.ledger@ == old(self).ledger@.drop_last());
350            assert(self.path@ == old(self).path@.drop_last());
351            assert(self.type_invariant()) by {
352                assert forall|i: int| 0 <= i < self.path.len()
353                    implies 1 <= #[trigger] self.path@[i] <= self.branch_factor by {
354                    assert(self.path@[i] == old(self).path@[i]);
355                }
356                assert forall|i: int| 0 <= i < self.ledger.len() implies {
357                    &&& #[trigger] self.ledger@[i].saved < 3
358                    &&& 1 <= self.ledger@[i].delta <= 2
359                } by {
360                    assert(self.ledger@[i].saved == old(self).ledger@[i].saved);
361                    assert(self.ledger@[i].delta == old(self).ledger@[i].delta);
362                }
363            }
364            assert(self.state_restoration()) by {
365                assert forall|i: int| 1 <= i < self.ledger.len() implies
366                    #[trigger] self.ledger@[i].saved as int
367                        == Self::mutate_spec(self.ledger@[i - 1].saved, self.ledger@[i - 1].delta) by {
368                    assert(self.ledger@[i].saved == old(self).ledger@[i].saved);
369                    assert(self.ledger@[i - 1].saved == old(self).ledger@[i - 1].saved);
370                    assert(self.ledger@[i - 1].delta == old(self).ledger@[i - 1].delta);
371                }
372            }
373            assert(self.pairing()) by {
374                if self.path.len() == 0 {
375                    assert(depth == 1);
376                    assert(old(self).ledger@[0].saved == self.init_aux);
377                } else {
378                    assert(depth >= 2);
379                    assert(old(self).ledger@[depth - 1].saved as int
380                        == Self::mutate_spec(
381                            old(self).ledger@[depth - 2].saved,
382                            old(self).ledger@[depth - 2].delta));
383                }
384            }
385        }
386    }
387}
388
389fn paths_equal(a: &Vec<u64>, b: &Vec<u64>) -> (same: bool)
390    ensures same == (a@ == b@),
391{
392    if a.len() != b.len() {
393        return false;
394    }
395    let len = a.len();
396    let mut i: usize = 0;
397    while i < len
398        invariant
399            i <= len,
400            len == a.len(),
401            len == b.len(),
402            forall|k: int| 0 <= k < i ==> #[trigger] a@[k] == b@[k],
403        decreases len - i,
404    {
405        if a[i] != b[i] {
406            return false;
407        }
408        i = i + 1;
409    }
410    assert(a@ =~= b@);
411    true
412}
413
414fn clone_path(p: &Vec<u64>) -> (out: Vec<u64>)
415    ensures out@ == p@,
416{
417    let mut out: Vec<u64> = Vec::new();
418    let n = p.len();
419    let mut i: usize = 0;
420    while i < n
421        invariant
422            i <= n,
423            n == p.len(),
424            out.len() == i,
425            forall|k: int| 0 <= k < i ==> out@[k] == p@[k],
426        decreases n - i,
427    {
428        out.push(p[i]);
429        i = i + 1;
430    }
431    assert(out@ =~= p@);
432    out
433}
434
435}