use vstd::prelude::*;
verus! {
pub open spec fn pow2(k: u64) -> int
decreases k,
{
if k == 0 { 1 } else { 2 * pow2((k - 1) as u64) }
}
pub proof fn lemma_pow2_positive(k: u64)
ensures pow2(k) >= 1,
decreases k,
{
if k > 0 {
lemma_pow2_positive((k - 1) as u64);
}
}
pub proof fn lemma_pow2_step(k: u64)
requires k > 0,
ensures pow2(k) == 2 * pow2((k - 1) as u64),
{
}
pub struct Bisection {
pub domain_size: u64,
pub max_probes: u64,
pub lo: u64,
pub hi: u64,
pub threshold: u64,
pub probes_taken: u64,
}
impl Bisection {
pub open spec fn type_invariant(&self) -> bool {
&&& self.domain_size >= 2
&&& 1 <= self.threshold < self.domain_size
&&& self.lo <= self.hi
&&& self.hi <= self.domain_size
}
pub open spec fn monotonicity(&self) -> bool {
self.lo <= self.threshold && self.threshold <= self.hi
}
pub open spec fn domain_fits_probes(&self) -> bool {
self.domain_size as int <= pow2(self.max_probes)
}
pub open spec fn probe_bound(&self) -> bool {
self.probes_taken <= self.max_probes
}
pub open spec fn width_exp_bound(&self) -> bool {
&&& self.probe_bound()
&&& self.hi as int - self.lo as int
<= pow2((self.max_probes - self.probes_taken) as u64)
}
pub open spec fn invariant(&self) -> bool {
&&& self.type_invariant()
&&& self.monotonicity()
&&& self.domain_fits_probes()
&&& self.width_exp_bound()
}
pub open spec fn mid(&self) -> int {
self.lo as int + (self.hi as int - self.lo as int) / 2
}
pub fn new(
lo: u64,
hi: u64,
threshold: u64,
domain_size: u64,
max_probes: u64,
) -> (b: Bisection)
requires
domain_size >= 2,
1 <= threshold < domain_size,
lo <= threshold,
threshold <= hi,
hi <= domain_size,
domain_size as int <= pow2(max_probes),
ensures
b.domain_size == domain_size,
b.max_probes == max_probes,
b.lo == lo,
b.hi == hi,
b.threshold == threshold,
b.probes_taken == 0,
b.invariant(),
{
proof {
lemma_pow2_positive(max_probes);
assert(hi as int - lo as int <= domain_size as int);
}
Bisection {
domain_size,
max_probes,
lo,
hi,
threshold,
probes_taken: 0,
}
}
pub fn converged(&self) -> (c: bool)
requires
self.invariant(),
ensures
c == (self.hi - self.lo < 2),
{
self.hi - self.lo < 2
}
pub fn probe(&mut self)
requires
old(self).hi - old(self).lo >= 2,
old(self).invariant(),
ensures
final(self).invariant(),
final(self).hi - final(self).lo <= (old(self).hi - old(self).lo) / 2,
final(self).hi - final(self).lo < old(self).hi - old(self).lo,
final(self).domain_size == old(self).domain_size,
final(self).max_probes == old(self).max_probes,
final(self).threshold == old(self).threshold,
crate::connectives::cursor::cursor_admitted(
old(self).lo as nat,
final(self).lo as nat,
),
final(self).probes_taken == old(self).probes_taken + 1,
(final(self).hi as int == old(self).mid() && final(self).lo == old(self).lo)
|| (final(self).lo as int == old(self).mid() + 1
&& final(self).hi == old(self).hi),
{
let old_lo = self.lo;
let old_hi = self.hi;
let old_width = self.hi - self.lo;
let old_probes = self.probes_taken;
let remaining = self.max_probes - self.probes_taken;
let _ = (old_lo, old_hi, old_width, remaining);
proof {
if self.probes_taken == self.max_probes {
assert(remaining == 0);
assert(pow2(remaining) == 1);
assert(self.hi as int - self.lo as int <= 1);
assert(false);
}
assert(self.probes_taken < self.max_probes);
assert(remaining > 0);
lemma_pow2_step(remaining);
lemma_pow2_positive((remaining - 1) as u64);
}
let mid = self.lo + (self.hi - self.lo) / 2;
if mid >= self.threshold {
self.hi = mid;
} else {
self.lo = mid + 1;
}
self.probes_taken = old_probes + 1;
proof {
assert(self.hi - self.lo <= old_width / 2);
assert(old_width as int <= pow2(remaining));
assert(old_width as int / 2 <= pow2((remaining - 1) as u64));
assert(self.hi as int - self.lo as int <= old_width as int / 2);
assert(self.max_probes - self.probes_taken == remaining - 1);
assert(self.hi as int - self.lo as int
<= pow2((self.max_probes - self.probes_taken) as u64));
assert(old_lo <= old_hi);
}
}
pub fn bisect(&mut self)
requires
old(self).invariant(),
ensures
final(self).invariant(),
final(self).hi - final(self).lo < 2,
final(self).domain_size == old(self).domain_size,
final(self).max_probes == old(self).max_probes,
final(self).threshold == old(self).threshold,
{
while self.hi - self.lo >= 2
invariant
self.invariant(),
self.domain_size == old(self).domain_size,
self.max_probes == old(self).max_probes,
self.threshold == old(self).threshold,
decreases self.hi - self.lo,
{
self.probe();
}
}
}
pub open spec fn is_sorted(s: Seq<u64>) -> bool {
forall|i: int, j: int| 0 <= i <= j < s.len() ==> s[i] <= s[j]
}
pub fn bisection_find(sorted: &[u64], target: u64) -> (idx: usize)
requires
is_sorted(sorted@),
ensures
idx as int <= sorted@.len() as int,
idx < sorted@.len() ==> sorted@[idx as int] == target,
{
let n: usize = sorted.len();
let mut lo: usize = 0;
let mut hi: usize = n;
while lo < hi
invariant
lo <= hi,
hi <= n,
n == sorted@.len(),
is_sorted(sorted@),
decreases hi - lo,
{
let mid = lo + (hi - lo) / 2;
let mid_val = sorted[mid];
if mid_val == target {
return mid;
} else if mid_val < target {
lo = mid + 1;
} else {
hi = mid;
}
}
n }
}