Skip to main content

automation_structures/compositions/
relationship_graph.rs

1// RelationshipGraph assembled from the ResourceRegistry owner.
2//
3// The formal reduction stores each weighted edge as one ResourceRegistry key. The graph's
4// adjacency relation is the source/destination projection of those keys; it is not a second
5// mutable graph representation. AddEdge and RemoveEdge therefore mutate registry state only by
6// calling ResourceRegistry actions.
7
8use vstd::prelude::*;
9
10use crate::primitives::resource_registry::ResourceRegistry;
11
12verus! {
13
14/// `(source, destination, weight)` registry key.
15pub type EdgeKey = (usize, usize, u64);
16/// Registry entry used to retain an edge without a second payload.
17pub type EdgeBinding = (EdgeKey, ());
18
19/// One weighted relationship is admitted by a RelationshipGraph universe.
20pub open spec fn edge_admitted(
21    num_nodes: usize,
22    max_weight: u64,
23    source: usize,
24    target: usize,
25    weight: u64,
26) -> bool {
27    source < num_nodes && target < num_nodes && weight <= max_weight
28}
29
30/// One adjacency relationship is admitted by a RelationshipGraph universe.
31pub open spec fn adjacency_admitted(
32    num_nodes: usize,
33    source: usize,
34    target: usize,
35) -> bool {
36    source < num_nodes && target < num_nodes
37}
38
39/// One adjacency answer agrees with its weighted-edge source projection.
40pub open spec fn adjacency_consistent(
41    adjacency_present: bool,
42    edge_present: bool,
43) -> bool {
44    crate::connectives::projection::membership_consistent(
45        adjacency_present,
46        edge_present,
47    )
48}
49
50/// A present relationship is not reflexive.
51pub open spec fn edge_irreflexive(present: bool, source: usize, target: usize) -> bool {
52    present ==> source != target
53}
54
55/// Whether a registry prefix contains any weighted edge from `source` to `target`.
56pub open spec fn has_edge(
57    entries: Seq<EdgeBinding>,
58    n: int,
59    source: usize,
60    target: usize,
61) -> bool {
62    exists|index: int|
63        0 <= index < n
64            && entries[index].0.0 == source
65            && entries[index].0.1 == target
66}
67
68/// Exact weighted-edge membership in a registry prefix.
69pub open spec fn has_exact_edge(
70    entries: Seq<EdgeBinding>,
71    n: int,
72    source: usize,
73    target: usize,
74    weight: u64,
75) -> bool {
76    crate::primitives::resource_registry::has_pair(
77        entries,
78        n,
79        (source, target, weight),
80        (),
81    )
82}
83
84/// Extending the registry prefix exposes the new edge exactly once at the new position.
85pub proof fn lemma_has_edge_extend(
86    entries: Seq<EdgeBinding>,
87    n: int,
88    source: usize,
89    target: usize,
90)
91    requires 0 <= n < entries.len(),
92    ensures
93        has_edge(entries, n + 1, source, target)
94            == (has_edge(entries, n, source, target)
95                || (entries[n].0.0 == source && entries[n].0.1 == target)),
96{
97    if has_edge(entries, n + 1, source, target) {
98        let index = choose|index: int|
99            0 <= index < n + 1
100                && entries[index].0.0 == source
101                && entries[index].0.1 == target;
102        assert(index < n || index == n);
103    }
104    if has_edge(entries, n, source, target) {
105        let index = choose|index: int|
106            0 <= index < n
107                && entries[index].0.0 == source
108                && entries[index].0.1 == target;
109        assert(0 <= index < n + 1);
110    }
111    if entries[n].0.0 == source && entries[n].0.1 == target {
112        assert(0 <= n < n + 1);
113    }
114}
115
116/// Appending one registered edge extends adjacency by exactly its endpoint pair.
117pub proof fn lemma_push_has_edge(
118    entries: Seq<(EdgeKey, ())>,
119    added: EdgeKey,
120    source: usize,
121    target: usize,
122)
123    ensures has_edge(entries.push((added, ())), entries.len() as int + 1, source, target)
124        == (has_edge(entries, entries.len() as int, source, target)
125            || (added.0 == source && added.1 == target)),
126{
127    let pushed = entries.push((added, ()));
128    if has_edge(pushed, pushed.len() as int, source, target) {
129        let index = choose|index: int|
130            0 <= index < pushed.len()
131                && pushed[index].0.0 == source
132                && pushed[index].0.1 == target;
133        if index < entries.len() {
134            assert(pushed[index] == entries[index]);
135        } else {
136            assert(index == entries.len());
137        }
138    }
139    if has_edge(entries, entries.len() as int, source, target) {
140        let index = choose|index: int|
141            0 <= index < entries.len()
142                && entries[index].0.0 == source
143                && entries[index].0.1 == target;
144        assert(pushed[index] == entries[index]);
145    }
146    if added.0 == source && added.1 == target {
147        assert(pushed[entries.len() as int].0 == added);
148    }
149}
150
151/// A weighted directed graph whose only mutable edge owner is ResourceRegistry.
152pub struct RelationshipGraph {
153    /// Number of nodes in the fixed universe.
154    pub num_nodes: usize,
155    /// Inclusive edge-weight ceiling.
156    pub max_weight: u64,
157    /// Owner of exact weighted edges.
158    pub registry: ResourceRegistry<EdgeKey, ()>,
159}
160
161impl RelationshipGraph {
162    /// Whether any registered weighted edge connects `source` to `target`.
163    pub open spec fn edge_proj(&self, source: usize, target: usize) -> bool {
164        has_edge(
165            self.registry.entries@,
166            self.registry.entries@.len() as int,
167            source,
168            target,
169        )
170    }
171
172    /// Whether the exact weighted edge is registered.
173    pub open spec fn exact_edge(&self, source: usize, target: usize, weight: u64) -> bool {
174        self.registry.maps_to((source, target, weight), ())
175    }
176
177    /// Exact weighted membership projects to endpoint adjacency.
178    pub proof fn exact_edge_implies_pair(&self, source: usize, target: usize, weight: u64)
179        ensures self.exact_edge(source, target, weight) ==> self.edge_proj(source, target),
180    {
181        if self.exact_edge(source, target, weight) {
182            let index = choose|index: int|
183                0 <= index < self.registry.entries@.len()
184                    && self.registry.entries@[index].0 == (source, target, weight)
185                    && self.registry.entries@[index].1 == ();
186            assert(self.registry.entries@[index].0.0 == source);
187            assert(self.registry.entries@[index].0.1 == target);
188        }
189    }
190
191    /// The formal adjacency variable is the edge registry's pair projection.
192    pub open spec fn adj_proj(&self, source: usize, target: usize) -> bool {
193        self.edge_proj(source, target)
194    }
195
196    /// The registry is unique and every registered edge is in the configured universe.
197    pub open spec fn type_invariant(&self) -> bool {
198        &&& self.registry.unique_mapping()
199        &&& forall|index: int|
200            #![trigger self.registry.entries@[index]]
201            0 <= index < self.registry.entries@.len() ==> edge_admitted(
202                self.num_nodes,
203                self.max_weight,
204                self.registry.entries@[index].0.0,
205                self.registry.entries@[index].0.1,
206                self.registry.entries@[index].0.2,
207            )
208    }
209
210    /// The adjacency projection and weighted-edge projection are the same derived relation.
211    pub open spec fn adjacency_consistency(&self) -> bool {
212        forall|source: usize, target: usize|
213            source < self.num_nodes && target < self.num_nodes ==> adjacency_consistent(
214                #[trigger] self.adj_proj(source, target),
215                self.edge_proj(source, target),
216            )
217    }
218
219    /// No registered edge is a self-loop.
220    pub open spec fn no_self_loops(&self) -> bool {
221        forall|index: int|
222            #![trigger self.registry.entries@[index]]
223            0 <= index < self.registry.entries@.len() ==> edge_irreflexive(
224                true,
225                self.registry.entries@[index].0.0,
226                self.registry.entries@[index].0.1,
227            )
228    }
229
230    /// Whether the edge registry and its derived adjacency relation are valid.
231    pub open spec fn inv(&self) -> bool {
232        self.type_invariant() && self.adjacency_consistency() && self.no_self_loops()
233    }
234
235    /// Storage facts needed by larger compositions using the graph owner.
236    pub proof fn expose_storage_facts(&self)
237        requires self.inv(),
238        ensures
239            self.registry.unique_mapping(),
240            forall|index: int| #![trigger self.registry.entries@[index]]
241                0 <= index < self.registry.entries@.len() ==>
242                    self.registry.entries@[index].0.0 < self.num_nodes
243                        && self.registry.entries@[index].0.1 < self.num_nodes
244                        && self.registry.entries@[index].0.2 <= self.max_weight
245                        && self.registry.entries@[index].0.0
246                            != self.registry.entries@[index].0.1,
247    {
248        reveal(RelationshipGraph::inv);
249        reveal(RelationshipGraph::type_invariant);
250        reveal(RelationshipGraph::no_self_loops);
251        reveal(edge_admitted);
252        reveal(edge_irreflexive);
253    }
254
255    /// Construct an empty graph from an empty edge registry.
256    pub fn new(num_nodes: usize, max_weight: u64) -> (graph: RelationshipGraph)
257        ensures
258            graph.num_nodes == num_nodes,
259            graph.max_weight == max_weight,
260            graph.registry.entries@.len() == 0,
261            graph.inv(),
262    {
263        let registry = ResourceRegistry::new();
264        RelationshipGraph { num_nodes, max_weight, registry }
265    }
266
267    /// Whether an exact weighted edge can be inserted.
268    pub fn can_add_edge(&self, source: usize, target: usize, weight: u64) -> (enabled: bool)
269        ensures enabled == (source < self.num_nodes
270            && target < self.num_nodes
271            && weight <= self.max_weight
272            && source != target),
273    {
274        source < self.num_nodes
275            && target < self.num_nodes
276            && weight <= self.max_weight
277            && source != target
278    }
279
280    /// Query exact membership through the ResourceRegistry owner.
281    pub fn contains_exact_edge(
282        &self,
283        source: usize,
284        target: usize,
285        weight: u64,
286    ) -> (present: bool)
287        requires self.registry.unique_mapping(),
288        ensures present == self.exact_edge(source, target, weight),
289    {
290        match self.registry.lookup((source, target, weight)) {
291            Some(_) => true,
292            None => false,
293        }
294    }
295
296    /// Scan the edge registry to answer the derived adjacency query.
297    pub fn contains_pair(&self, source: usize, target: usize) -> (present: bool)
298        ensures present == self.edge_proj(source, target),
299    {
300        let length = self.registry.entries.len();
301        let mut index: usize = 0;
302        while index < length
303            invariant
304                index <= length,
305                length == self.registry.entries.len(),
306                !has_edge(self.registry.entries@, index as int, source, target),
307            decreases length - index,
308        {
309            let key = self.registry.entries[index].0;
310            if key.0 == source && key.1 == target {
311                return true;
312            }
313            proof {
314                lemma_has_edge_extend(
315                    self.registry.entries@,
316                    index as int,
317                    source,
318                    target,
319                );
320            }
321            index = index + 1;
322        }
323        false
324    }
325
326    /// Register one exact weighted edge.
327    pub fn add_edge(
328        &mut self,
329        source: usize,
330        target: usize,
331        weight: u64,
332    ) -> (added: bool)
333        requires
334            old(self).inv(),
335            source < old(self).num_nodes,
336            target < old(self).num_nodes,
337            weight <= old(self).max_weight,
338            source != target,
339        ensures
340            final(self).inv(),
341            final(self).num_nodes == old(self).num_nodes,
342            final(self).max_weight == old(self).max_weight,
343            added == !old(self).exact_edge(source, target, weight),
344            !added ==> final(self).registry.entries@ == old(self).registry.entries@,
345            added ==> final(self).registry.entries@
346                == old(self).registry.entries@.push(((source, target, weight), ())),
347            forall|other_source: usize, other_target: usize|
348                #[trigger] final(self).edge_proj(other_source, other_target)
349                    == (old(self).edge_proj(other_source, other_target)
350                        || (other_source == source && other_target == target)),
351    {
352        proof { self.expose_storage_facts(); }
353        if self.contains_exact_edge(source, target, weight) {
354            return false;
355        }
356        let ghost before = self.registry.entries@;
357        self.registry.register((source, target, weight), ());
358        assert(self.registry.entries@ == before.push(((source, target, weight), ())));
359        assert forall|other_source: usize, other_target: usize|
360            #[trigger] self.edge_proj(other_source, other_target)
361                == (has_edge(before, before.len() as int, other_source, other_target)
362                    || (other_source == source && other_target == target)) by {
363            lemma_push_has_edge(
364                before,
365                (source, target, weight),
366                other_source,
367                other_target,
368            );
369        }
370        assert(self.type_invariant()) by {
371            assert forall|index: int| #![trigger self.registry.entries@[index]]
372                0 <= index < self.registry.entries@.len() implies edge_admitted(
373                    self.num_nodes,
374                    self.max_weight,
375                    self.registry.entries@[index].0.0,
376                    self.registry.entries@[index].0.1,
377                    self.registry.entries@[index].0.2,
378                ) by {
379                if index < before.len() {
380                    assert(self.registry.entries@[index] == before[index]);
381                } else {
382                    assert(index == before.len());
383                    assert(self.registry.entries@[index] == ((source, target, weight), ()));
384                }
385            }
386        }
387        assert(self.no_self_loops()) by {
388            assert forall|index: int| #![trigger self.registry.entries@[index]]
389                0 <= index < self.registry.entries@.len() implies edge_irreflexive(
390                    true,
391                    self.registry.entries@[index].0.0,
392                    self.registry.entries@[index].0.1,
393                ) by {
394                if index < before.len() {
395                    assert(self.registry.entries@[index] == before[index]);
396                } else {
397                    assert(index == before.len());
398                    assert(self.registry.entries@[index] == ((source, target, weight), ()));
399                }
400            }
401        }
402        assert(self.adjacency_consistency()) by {
403            reveal(RelationshipGraph::adjacency_consistency);
404            reveal(RelationshipGraph::adj_proj);
405            reveal(adjacency_consistent);
406            reveal(crate::connectives::projection::membership_consistent);
407        }
408        true
409    }
410
411    /// Remove every registered weight for one source/destination pair.
412    pub fn remove_edge(&mut self, source: usize, target: usize)
413        requires old(self).inv(),
414        ensures
415            final(self).inv(),
416            final(self).num_nodes == old(self).num_nodes,
417            final(self).max_weight == old(self).max_weight,
418            forall|s: usize, d: usize, weight: u64|
419                #[trigger] final(self).exact_edge(s, d, weight)
420                    == (!(s == source && d == target)
421                        && old(self).exact_edge(s, d, weight)),
422            !final(self).edge_proj(source, target),
423    {
424        proof { self.expose_storage_facts(); }
425        let ghost original = self.registry.entries@;
426        let ghost original_num_nodes = self.num_nodes;
427        let ghost original_max_weight = self.max_weight;
428        let mut index: usize = 0;
429        while index < self.registry.entries.len()
430            invariant
431                index <= self.registry.entries.len(),
432                self.num_nodes == original_num_nodes,
433                self.max_weight == original_max_weight,
434                self.registry.unique_mapping(),
435                forall|entry: int| #![trigger self.registry.entries@[entry]]
436                    0 <= entry < self.registry.entries@.len() ==>
437                        self.registry.entries@[entry].0.0 < self.num_nodes
438                            && self.registry.entries@[entry].0.1 < self.num_nodes
439                            && self.registry.entries@[entry].0.2 <= self.max_weight
440                            && self.registry.entries@[entry].0.0
441                                != self.registry.entries@[entry].0.1,
442                forall|entry: int| #![trigger self.registry.entries@[entry]]
443                    0 <= entry < index ==>
444                        !(self.registry.entries@[entry].0.0 == source
445                            && self.registry.entries@[entry].0.1 == target),
446                forall|s: usize, d: usize, weight: u64|
447                    !(s == source && d == target) ==>
448                        (#[trigger] has_exact_edge(
449                            self.registry.entries@,
450                            self.registry.entries@.len() as int,
451                            s,
452                            d,
453                            weight,
454                        ) == has_exact_edge(
455                            original,
456                            original.len() as int,
457                            s,
458                            d,
459                            weight,
460                        )),
461            decreases self.registry.entries.len() - index,
462        {
463            let key = self.registry.entries[index].0;
464            if key.0 == source && key.1 == target {
465                let ghost before = self.registry.entries@;
466                let _removed = self.registry.deregister_at(index);
467                assert(_removed.0 == key);
468                assert forall|entry: int| #![trigger self.registry.entries@[entry]]
469                    0 <= entry < self.registry.entries@.len() implies
470                        self.registry.entries@[entry].0.0 < self.num_nodes
471                            && self.registry.entries@[entry].0.1 < self.num_nodes
472                            && self.registry.entries@[entry].0.2 <= self.max_weight
473                            && self.registry.entries@[entry].0.0
474                                != self.registry.entries@[entry].0.1 by {
475                    before.remove_ensures(index as int);
476                    let old_entry = if entry < index { entry } else { entry + 1 };
477                    assert(0 <= old_entry < before.len());
478                    assert(self.registry.entries@[entry] == before[old_entry]);
479                }
480                assert forall|entry: int| #![trigger self.registry.entries@[entry]]
481                    0 <= entry < index implies
482                        !(self.registry.entries@[entry].0.0 == source
483                            && self.registry.entries@[entry].0.1 == target) by {
484                    before.remove_ensures(index as int);
485                    assert(self.registry.entries@[entry] == before[entry]);
486                }
487                assert forall|s: usize, d: usize, weight: u64|
488                    !(s == source && d == target) implies
489                        (#[trigger] has_exact_edge(
490                            self.registry.entries@,
491                            self.registry.entries@.len() as int,
492                            s,
493                            d,
494                            weight,
495                        ) == has_exact_edge(
496                            original,
497                            original.len() as int,
498                            s,
499                            d,
500                            weight,
501                    )) by {
502                    assert((s, d, weight) != key);
503                    assert(self.registry.maps_to((s, d, weight), ()) ==
504                        (has_exact_edge(
505                            before,
506                            before.len() as int,
507                            s,
508                            d,
509                            weight,
510                        ) && (s, d, weight) != key));
511                    assert(has_exact_edge(
512                        before,
513                        before.len() as int,
514                        s,
515                        d,
516                        weight,
517                    ) == has_exact_edge(
518                        original,
519                        original.len() as int,
520                        s,
521                        d,
522                        weight,
523                    ));
524                }
525            } else {
526                index = index + 1;
527            }
528        }
529        assert(!self.edge_proj(source, target)) by {
530            if self.edge_proj(source, target) {
531                let entry = choose|entry: int|
532                    0 <= entry < self.registry.entries@.len()
533                        && self.registry.entries@[entry].0.0 == source
534                        && self.registry.entries@[entry].0.1 == target;
535                assert(false);
536            }
537        }
538        assert(self.type_invariant());
539        assert(self.no_self_loops());
540        assert(self.adjacency_consistency()) by {
541            reveal(RelationshipGraph::adjacency_consistency);
542            reveal(RelationshipGraph::adj_proj);
543            reveal(adjacency_consistent);
544            reveal(crate::connectives::projection::membership_consistent);
545        }
546        assert forall|s: usize, d: usize, weight: u64|
547            #[trigger] self.exact_edge(s, d, weight)
548                == (!(s == source && d == target)
549                    && old(self).exact_edge(s, d, weight)) by {
550            if s == source && d == target {
551                if self.exact_edge(s, d, weight) {
552                    let entry = choose|entry: int|
553                        0 <= entry < self.registry.entries@.len()
554                            && self.registry.entries@[entry].0 == (s, d, weight)
555                            && self.registry.entries@[entry].1 == ();
556                    assert(has_edge(
557                        self.registry.entries@,
558                        self.registry.entries@.len() as int,
559                        source,
560                        target,
561                    ));
562                    assert(self.edge_proj(source, target));
563                }
564            } else {
565                assert(self.exact_edge(s, d, weight) == has_exact_edge(
566                    self.registry.entries@,
567                    self.registry.entries@.len() as int,
568                    s,
569                    d,
570                    weight,
571                ));
572                assert(old(self).exact_edge(s, d, weight) == has_exact_edge(
573                    original,
574                    original.len() as int,
575                    s,
576                    d,
577                    weight,
578                ));
579            }
580        }
581    }
582}
583
584}