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