use vstd::prelude::*;
verus! {
pub open spec fn sum_to(s: Seq<u64>, n: int) -> int
decreases n,
{
if n <= 0 {
0
} else if n > s.len() as int {
0
} else {
s[n - 1] as int + sum_to(s, n - 1)
}
}
pub open spec fn sum_spec(s: Seq<u64>) -> int {
sum_to(s, s.len() as int)
}
pub proof fn lemma_sum_to_bounded(s: Seq<u64>, n: int)
requires
forall|k: int| 0 <= k < s.len() ==> s[k] <= 1_000_000_000u64,
0 <= n <= s.len() as int,
ensures
sum_to(s, n) <= 1_000_000_000 * n,
decreases n,
{
if n > 0 {
lemma_sum_to_bounded(s, n - 1);
}
}
proof fn lemma_sum_to_push_prefix(s: Seq<u64>, x: u64, n: int)
requires
0 <= n <= s.len() as int,
ensures
sum_to(s.push(x), n) == sum_to(s, n),
decreases n,
{
if n > 0 {
lemma_sum_to_push_prefix(s, x, n - 1);
assert(s.push(x)[n - 1] == s[n - 1]);
}
}
pub proof fn lemma_sum_push(s: Seq<u64>, x: u64)
ensures
sum_spec(s.push(x)) == sum_spec(s) + x as int,
{
let t = s.push(x);
let n = s.len() as int;
assert(t.len() == n + 1);
lemma_sum_to_push_prefix(s, x, n);
assert(t[n] == x);
assert(sum_to(t, n + 1) == t[n] as int + sum_to(t, n));
}
pub struct Reducer {
pub result: u64,
pub processed: Vec<u64>,
pub remaining: Vec<u64>,
pub original: Ghost<Seq<u64>>,
}
impl Reducer {
pub open spec fn partition(&self) -> bool {
crate::connectives::accumulator::carries(
self.original@,
self.processed@,
self.remaining@,
)
}
pub open spec fn aggregate(&self) -> bool {
self.result as int == sum_spec(self.processed@)
}
pub open spec fn bounded(&self) -> bool {
&&& forall|k: int| 0 <= k < self.processed@.len() ==> self.processed@[k] <= 1_000_000_000u64
&&& forall|k: int| 0 <= k < self.remaining@.len() ==> self.remaining@[k] <= 1_000_000_000u64
&&& self.original@.len() <= 1_000_000_000
}
pub fn new(items: Vec<u64>) -> (r: Reducer)
requires
forall|k: int| 0 <= k < items@.len() ==> items@[k] <= 1_000_000_000u64,
items@.len() <= 1_000_000_000,
ensures
r.partition(),
r.aggregate(),
r.bounded(),
r.original@ == items@,
r.remaining@ == items@,
{
let ghost orig = items@;
let r = Reducer {
result: 0,
processed: Vec::new(),
remaining: items,
original: Ghost(orig),
};
proof {
assert(r.processed@.len() == 0);
assert(r.processed@ + r.remaining@ =~= r.original@);
assert(sum_spec(r.processed@) == 0);
}
r
}
pub fn done(&self) -> (d: bool)
ensures
d == (self.remaining@.len() == 0),
{
self.remaining.len() == 0
}
pub fn process(&mut self)
requires
old(self).remaining@.len() > 0,
old(self).partition(),
old(self).aggregate(),
old(self).bounded(),
ensures
final(self).partition(),
final(self).aggregate(),
final(self).bounded(),
final(self).original@ == old(self).original@,
final(self).remaining@.len() == old(self).remaining@.len() - 1,
final(self).processed@ == old(self).processed@.push(old(self).remaining@[0]),
final(self).remaining@ == old(self).remaining@.subrange(
1, old(self).remaining@.len() as int),
final(self).result == old(self).result + old(self).remaining@[0],
{
let ghost p = self.processed@;
let ghost r = self.remaining@;
let x = self.remaining[0];
assert(crate::connectives::ordering_pass::selects_first(self.remaining@, x));
assert(self.processed@.len() <= self.original@.len()) by {
assert((p + r).len() == p.len() + r.len());
}
proof {
lemma_sum_to_bounded(self.processed@, self.processed@.len() as int);
}
let removed = self.remaining.remove(0);
let _ = removed;
self.processed.push(x);
self.result = self.result + x;
proof {
lemma_sum_push(p, x);
assert(self.remaining@ =~= r.subrange(1, r.len() as int));
assert(self.processed@ =~= p.push(x));
assert(r[0] == x);
assert(self.processed@ + self.remaining@ =~= self.original@);
assert(sum_spec(self.processed@) == sum_spec(p) + x as int);
assert(forall|k: int| 0 <= k < self.remaining@.len()
==> self.remaining@[k] <= 1_000_000_000u64);
assert(forall|k: int| 0 <= k < self.processed@.len()
==> self.processed@[k] <= 1_000_000_000u64);
}
}
}
pub open spec fn fold_to(s: Seq<u64>, n: int, identity: u64, op: spec_fn(u64, u64) -> u64) -> u64
decreases n,
{
if n <= 0 {
identity
} else if n > s.len() as int {
identity
} else {
op(fold_to(s, n - 1, identity, op), s[n - 1])
}
}
proof fn lemma_fold_to_bounded_sum(s: Seq<u64>, n: int)
requires
forall|k: int| 0 <= k < s.len() ==> s[k] <= 1_000_000_000u64,
0 <= n <= s.len() as int,
ensures
fold_to(s, n, 0, |a: u64, b: u64| (a + b) as u64) <= 1_000_000_000 * n,
decreases n,
{
if n > 0 {
lemma_fold_to_bounded_sum(s, n - 1);
}
}
pub fn reduce_sum(items: &[u64]) -> (result: u64)
requires
forall|k: int| 0 <= k < items@.len() ==> items@[k] <= 1_000_000_000u64,
items@.len() <= 1_000_000_000,
ensures
result as int == fold_to(items@, items@.len() as int, 0, |a: u64, b: u64| (a + b) as u64) as int,
{
let n: usize = items.len();
let mut result: u64 = 0;
let mut i: usize = 0;
while i < n
invariant
i <= n,
n == items@.len(),
n <= 1_000_000_000,
forall|k: int| 0 <= k < items@.len() ==> items@[k] <= 1_000_000_000u64,
result as int == fold_to(items@, i as int, 0, |a: u64, b: u64| (a + b) as u64) as int,
decreases n - i,
{
proof {
lemma_fold_to_bounded_sum(items@, i as int);
}
result = result + items[i];
i = i + 1;
}
result
}
pub fn reduce_max(items: &[u64]) -> (result: u64)
requires
forall|k: int| 0 <= k < items@.len() ==> items@[k] <= 1_000_000_000u64,
items@.len() <= 1_000_000_000,
ensures
result as int == fold_to(items@, items@.len() as int, 0, |a: u64, b: u64| if a > b { a } else { b }) as int,
{
let n: usize = items.len();
let mut result: u64 = 0;
let mut i: usize = 0;
while i < n
invariant
i <= n,
n == items@.len(),
forall|k: int| 0 <= k < items@.len() ==> items@[k] <= 1_000_000_000u64,
result <= 1_000_000_000u64,
result as int == fold_to(items@, i as int, 0, |a: u64, b: u64| if a > b { a } else { b }) as int,
decreases n - i,
{
if items[i] > result {
result = items[i];
}
i = i + 1;
}
result
}
}