use vstd::prelude::*;
verus! {
pub struct AllocationSnapshot {
pub capacity: u64,
pub num_nodes: u64,
pub accepted: Vec<u64>,
pub total_cost: u64,
pub budget_remaining: u64,
}
impl AllocationSnapshot {
pub open spec fn accepted_subset_nodes(&self) -> bool {
forall|i: int|
0 <= i < self.accepted.len() ==> #[trigger] self.accepted@[i] < self.num_nodes
}
pub open spec fn accepted_distinct(&self) -> bool {
forall|i: int, j: int|
0 <= i < self.accepted.len() && 0 <= j < self.accepted.len() && i != j
==> self.accepted@[i] != self.accepted@[j]
}
pub open spec fn type_invariant(&self) -> bool {
self.accepted_subset_nodes() && self.accepted_distinct()
}
pub open spec fn budget_consistency(&self) -> bool {
self.total_cost + self.budget_remaining <= self.capacity
}
pub open spec fn contains(&self, n: u64) -> bool {
exists|i: int| 0 <= i < self.accepted.len() && self.accepted@[i] == n
}
pub fn new(capacity: u64, num_nodes: u64) -> (s: AllocationSnapshot)
ensures
s.capacity == capacity,
s.num_nodes == num_nodes,
s.accepted@.len() == 0,
s.total_cost == 0,
s.budget_remaining == capacity,
s.type_invariant(),
s.budget_consistency(),
{
AllocationSnapshot {
capacity,
num_nodes,
accepted: Vec::new(),
total_cost: 0,
budget_remaining: capacity,
}
}
pub fn contains_exec(&self, n: u64) -> (b: bool)
ensures
b == self.contains(n),
{
let len = self.accepted.len();
let mut i: usize = 0;
while i < len
invariant
i <= len,
len == self.accepted.len(),
forall|k: int| 0 <= k < i ==> self.accepted@[k] != n,
decreases len - i,
{
if self.accepted[i] == n {
assert(self.accepted@[i as int] == n);
return true;
}
i = i + 1;
}
false
}
pub fn accept_node(&mut self, n: u64, node_cost: u64)
requires
old(self).type_invariant(),
old(self).budget_consistency(),
n < old(self).num_nodes, !old(self).contains(n), 1 <= node_cost, node_cost <= old(self).budget_remaining, ensures
final(self).capacity == old(self).capacity,
final(self).num_nodes == old(self).num_nodes,
final(self).total_cost == old(self).total_cost + node_cost,
final(self).budget_remaining == old(self).budget_remaining - node_cost,
final(self).accepted@ == old(self).accepted@.push(n),
final(self).contains(n),
final(self).type_invariant(),
final(self).budget_consistency(),
{
assert(self.total_cost + node_cost <= self.capacity);
self.accepted.push(n);
self.total_cost = self.total_cost + node_cost;
self.budget_remaining = self.budget_remaining - node_cost;
assert(self.contains(n)) by {
assert(self.accepted@[old(self).accepted@.len() as int] == n);
}
}
}
pub fn capture(capacity: u64, num_nodes: u64, nodes: &[u64], costs: &[u64])
-> (s: AllocationSnapshot)
requires
nodes@.len() == costs@.len(),
ensures
s.capacity == capacity,
s.num_nodes == num_nodes,
s.type_invariant(),
s.budget_consistency(),
{
let mut s = AllocationSnapshot::new(capacity, num_nodes);
let n_reqs = nodes.len();
let mut i: usize = 0;
while i < n_reqs
invariant
i <= n_reqs,
n_reqs == nodes@.len(),
nodes@.len() == costs@.len(),
s.capacity == capacity,
s.num_nodes == num_nodes,
s.type_invariant(),
s.budget_consistency(),
decreases n_reqs - i,
{
let n = nodes[i];
let c = costs[i];
if n < num_nodes && 1 <= c && c <= s.budget_remaining && !s.contains_exec(n) {
s.accept_node(n, c);
}
i = i + 1;
}
s
}
}