Files
why3/examples/pqueue.mlw
Jean-Christophe Filliatre f6f80d659d rendons à César...
2025-11-21 13:26:28 +01:00

613 lines
21 KiB
Plaintext

(** {1 Priority Queues}
Below is a traditional implementation of priority queues (module
`Pqueue`) as a binary heap stored inside a resizeable array (module
`Vector`).
The code below was developed and verified as part of the VOCaL project
(A Verified OCaml Library, project ANR-15-CE25-0008 funded by the French
Agence Nationale de la Recherche, 2015--2021).
This file can be translated to OCaml with the following command:
{h<pre>
why3 extract -D ocaml64 pqueue.mlw --modular -o /dest/dir
</pre>}
The resulting file `pqueue__Pqueue.ml` provides an implementation of
priority queues in a functor `Make`, with the following interface:
{h<pre>
module Make(X : sig
type t
val dummy : t
val compare : t -> t -> int
end) : sig
type elt = X.t
type heap
val create : unit -> heap
val is_empty : heap -> bool
val size : heap -> int
exception Empty
val find_min_exn : heap -> X.t
val find_min : heap -> X.t option
val extract_min_exn : heap -> X.t
val delete_min_exn : heap -> unit
val insert : X.t -> heap -> unit
end
</pre>}
Authors: Aymeric Walch (ENS Lyon)
Mário Pereira (NOVA LINCS)
Jean-Christophe Filliâtre (CNRS)
*)
module Vector
use import mach.array.Array63 as A
use int.Int
use int.ComputerDivision
use int.MinMax as MinMax
use seq.Seq
use mach.int.MinMax63
use mach.int.Int63
use mach.int.Refint63
use option.Option
use import map.Map as M
use ref.Ref
use seq.Seq
use ocaml.Sys
use ocaml.Pervasives
type t 'a = {
dummy: 'a;
mutable size: int63;
mutable data: A.array 'a;
ghost mutable view: seq 'a;
} invariant { length view = size }
invariant { forall i. 0 <= i < size -> view[i] = data[i] }
invariant { 0 <= size <= length data <= max_array_length }
invariant { forall i. size <= i < length data -> data[i] = dummy }
by {
dummy = any 'a; size = zero; data = A.make zero (any 'a); view = empty;
}
let create (capacity [@ocaml:optional]: option int63)
(dummy [@ocaml:named]: 'a) : t 'a
requires { let capacity =
match capacity with None -> zero | Some c -> c end in
0 <= capacity <= max_array_length }
ensures { result.size = 0 }
= let capacity = match capacity with None -> zero | Some c -> c end in
{ dummy = dummy; size = zero; data = A.make capacity dummy; view = empty }
let make (dummy [@ocaml:optional]: option 'a) (n: int63) (x: 'a) : t 'a
requires { 0 <= n <= max_array_length }
returns { a -> n = a.size = A.length a.data }
returns { a -> forall i. 0 <= i < n -> Seq.([]) a.view i = x }
returns { a -> a.dummy = match dummy with None -> x | Some d -> d end }
= let dummy = match dummy with None -> x | Some d -> d end in
{ dummy = dummy; size = n; data = A.make n x;
view = Seq.create (to_int n) (fun _ -> x) }
let init (dummy [@ocaml:named]: 'a) (n: int63) (f: int63 -> 'a) : t 'a
requires { 0 <= n <= max_array_length }
returns { a -> n = a.size }
returns { a -> forall i: int63. 0 <= i < n -> Seq.([]) a.view i = f i }
= let a = make None n dummy in
for i = 0 to n - 1 do
invariant { forall j:int63. 0 <= j < i -> Seq.([]) a.view j = f j }
invariant { forall j. 0 <= j < i -> Seq.([]) a.view j = a.data[j] }
invariant { Seq.length a.view = a.size }
invariant { forall j. i <= j < A.length a.data ->
Seq.([]) a.view j = a.dummy = a.data[j] }
A.([]<-) a.data i (f i);
a.view <- Seq.set a.view (to_int i) (f i);
done;
a
let length (a: t 'a) : int63
returns { n -> n = a.size }
= a.size
let get (a: t 'a) (i: int63) : 'a
requires { 0 <= i < a.size }
returns { x -> x = Seq.([]) a.view i }
= A.([]) a.data i
let set (a: t 'a) (n: int63) (x: 'a) : unit
requires { 0 <= n < a.size }
ensures { a.data.A.elts = (old a).data.A.elts[n <- x] }
= A.([]<-) a.data n x; a.view <- Seq.set a.view (to_int n) x
val ghost seq_fill (s: seq 'a) (ofs: int) (len: int) (v: 'a) : seq 'a
requires { 0 <= ofs /\ 0 <= len /\ ofs + len <= Seq.length s }
ensures { Seq.length s = Seq.length result }
ensures { forall i. (0 <= i < ofs \/ ofs + len <= i < Seq.length s) ->
Seq.get s i = Seq.get result i }
ensures { forall i. ofs <= i < ofs + len -> Seq.get result i = v }
let unsafe_resize (a: t 'a) (n: int63) : unit
requires { 0 <= n <= max_array_length }
ensures { n = a.size }
ensures { forall i. 0 <= i < MinMax.min ((old a).size) n ->
Seq.([]) a.view i = Seq.([]) (old a).view i }
= let n_old = A.length a.data in
if n <= a.size then
(* shrink *)
if n < n_old / 4 then begin (* reallocate into a smaller array *)
a.data <- A.sub a.data zero n;
a.view <- a.view[.. to_int n] end
else begin
A.fill a.data n (a.size - n) a.dummy;
a.view <- a.view[.. to_int n] end
else begin
(* grow *)
if n > n_old then begin (* reallocate into a larger array *)
let n_div2 = n / 2 in
let n' =
if n_div2 >= n_old then
if max_array_length / 2 >= n_div2 then n
else max_array_length
else if max_array_length / 2 >= n_old then 2 * n_old
else max_array_length in
let a' = A.make n' a.dummy in
A.blit a.data zero a' zero a.size;
a.data <- a';
end;
let ghost dummy = a.dummy in
let a_view' = Seq.create (to_int (n - a.size)) (fun _ -> dummy) in
a.view <- Seq.(++) a.view a_view';
end;
a.size <- n
let resize (a: t 'a) (n: int63) : unit
ensures { n = a.size }
ensures { forall i. 0 <= i < MinMax.min ((old a).size) n ->
Seq.([]) a.view i = Seq.([]) (old a).view i }
raises { Invalid_argument _ -> not (0 <= n <= max_array_length) }
= if not (zero <= n <= max_array_length) then raise Invalid_argument "resize";
unsafe_resize a n
(** Array interface *)
let clear (a: t 'a) : unit
ensures { a.size = 0 }
= unsafe_resize a zero
let is_empty (a: t 'a) : bool
returns { r -> r <-> a.size = 0 }
= length a = zero
let sub (a: t 'a) (ofs n: int63) : t 'a
requires { 0 <= ofs /\ 0 <= n /\ ofs + n <= a.size }
returns { r -> n = r.size }
returns { r -> forall i. 0 <= i < n ->
Seq.([]) r.view i = Seq.([]) a.view (ofs + i) }
= { dummy = a.dummy; size = n; data = A.sub a.data ofs n;
view = a.view[to_int ofs .. to_int (ofs + n)] }
let fill (a: t 'a) (ofs n: int63) (x: 'a) : unit
requires { 0 <= ofs /\ 0 <= n /\ ofs + n <= a.size }
writes { a.data.elts, a.view }
ensures { forall i. (0 <= i < ofs \/ ofs + n <= i < a.size) ->
Seq.([]) a.view i = Seq.([]) (old a).view i }
ensures { forall i. ofs <= i < ofs + n -> Seq.([]) a.view i = x }
= A.fill a.data ofs n x;
a.view <- seq_fill a.view (to_int ofs) (to_int n) x
val ghost seq_blit (s1: seq 'a) (ofs1: int) (s2: seq 'a) (ofs2: int)
(len: int) : seq 'a
requires { 0 <= ofs1 /\ 0 <= len /\ ofs1 + len <= Seq.length s1 }
requires { 0 <= ofs2 /\ ofs2 + len <= Seq.length s2 }
ensures { result = Seq.create (Seq.length s2)
(fun i -> if 0 <= i < ofs2 ||
ofs2 + len <= i < Seq.length s2 then
Seq.([]) s2 i
else Seq.([]) s1 (ofs1 + i - ofs2)) }
let blit (a1: t 'a) (ofs1: int63) (a2: t 'a) (ofs2: int63) (n: int63)
requires { 0 <= n }
requires { 0 <= ofs1 /\ ofs1 + n <= a1.size }
requires { 0 <= ofs2 /\ ofs2 + n <= a2.size }
writes { a2.data.elts, a2.view }
ensures { forall i. (0 <= i < ofs2 \/ ofs2 + n <= i < a2.size) ->
Seq.([]) a2.view i = Seq.([]) (old a2).view i }
ensures { forall i. ofs2 <= i < ofs2 + n ->
Seq.([]) a2.view i = Seq.([]) (old a1).view (ofs1 + i - ofs2) }
= A.blit a1.data ofs1 a2.data ofs2 n;
a2.view <- seq_blit a1.view (to_int ofs1) a2.view (to_int ofs2) (to_int n)
let append (a1: t 'a) (a2: t 'a)
requires { a1.size + a2.size <= max_array_length }
returns { a3 -> a3.size = a1.size + a2.size }
returns { a3 -> forall i. 0 <= i < a1.size ->
Seq.([]) a3.view i = Seq.([]) a1.view i }
returns { a3 -> forall i. 0 <= i < a2.size ->
Seq.([]) a3.view (a1.size + i) = Seq.([]) a2.view i }
= let n1 = length a1 in
let n2 = length a2 in
let a = make None (n1 + n2) a1.dummy in
blit a1 zero a zero n1;
blit a2 zero a n1 n2;
a
(** Stack interface *)
let push (a: t 'a) (x: 'a) : unit
requires { a.size < max_array_length }
ensures { a.size = (old a).size + 1 }
ensures { Seq.([]) a.view (a.size - 1) = x }
ensures { forall i. 0 <= i < (old a).size ->
Seq.([]) a.view i = Seq.([]) (old a).view i }
= let n = a.size in
unsafe_resize a (n+one);
a.view <- Seq.set a.view (to_int n) x;
A.([]<-) a.data n x
exception Empty
let pop (a: t 'a) : 'a
raises { Empty -> a.size = (old a).size = 0 }
returns { _ -> a.size = (old a).size - 1 }
returns { x -> x = Seq.([]) (old a).view (a.size) }
returns { _ -> forall i. 0 <= i < a.size ->
Seq.([]) a.view i = Seq.([]) (old a).view i }
= let n = length a - one in
if n < zero then raise Empty;
let r = A.([]) a.data n in
unsafe_resize a n;
r
let pop_opt (a: t 'a) : option 'a
returns { r -> match r with
| None -> a.size = (old a).size = 0
| Some x -> a.size = (old a).size - 1 /\
x = Seq.([]) (old a).view (a.size) /\
forall i. 0 <= i < a.size ->
Seq.([]) a.view i
= Seq.([]) (old a).view i end }
= let n = length a - one in
if n < zero then None else begin
let r = A.([]) a.data n in
unsafe_resize a n;
Some r
end
end
module Pqueue
use int.Int
use int.ComputerDivision
use int.Div2
use int.NumOf
use bag.Bag
use mach.int.Int63
use mach.int.Refint63
use import Vector as V
use seq.Seq
use option.Option
use ocaml.Pervasives
use ocaml.Sys
predicate is_pre_order (cmp: 'a -> 'a -> int63) =
(forall x. cmp x x = 0) /\
(forall x y. cmp x y = 0 <-> cmp y x = 0) /\
(forall x y. cmp x y < 0 <-> cmp y x > 0) /\
(forall x y z.
(cmp x y = 0 -> cmp y z = 0 -> cmp x z = 0) /\
(cmp x y = 0 -> cmp y z < 0 -> cmp x z < 0) /\
(cmp x y < 0 -> cmp y z = 0 -> cmp x z < 0) /\
(cmp x y < 0 -> cmp y z < 0 -> cmp x z < 0))
scope Make
scope X
type t
val dummy : t
function cmp : t -> t -> int63
axiom is_pre_order: is_pre_order cmp
val compare (x : t) (y : t) : int63
ensures { result = cmp x y }
end
type elt = X.t
predicate le (x y: elt) = X.cmp x y <= 0
function numocc (a: V.t elt) (c: elt) (l u: int) : int =
numof (fun i -> a.view[i] = c) l u
function numocc' (a : V.t elt) (c: elt) : int =
numocc a c 0 (length a.view)
predicate heap_order (a : V.t elt) = forall i.
(0 < 2*i + 1 < length a.view -> le a.view[i] a.view[2*i + 1]) /\
(0 < 2*i + 2 < length a.view -> le a.view[i] a.view[2*i + 2])
type heap = {
a : V.t elt;
ghost mutable bag : bag elt;
}
invariant { length a.view = card bag }
invariant { forall x. numocc' a x = nb_occ x bag }
invariant { heap_order a } (* Heap order is preserved *)
by { a = V.create (Some (0 : int63)) X.dummy; bag = empty_bag }
function minimum (h: heap) : elt
axiom min_def: forall h. card h.bag <> 0 -> minimum h = h.a.view[0]
predicate is_minimum (x: elt) (h: heap) =
mem x h.bag && forall e. mem e h.bag -> X.cmp x e <= 0
lemma num_exist:
forall h x. numocc' h.a x > 0 ->
exists i. 0 <= i < length h.a.view /\ h.a.view[i] = x
let lemma min_coherent (h: heap)
requires { 0 < card h.bag }
ensures { is_minimum (minimum h) h }
= let s = h.a.view in
let n = length s in
let rec lemma ismin (i: int)
requires { 0 <= i < n } variant { i } ensures { le s[0] s[i] }
= if i > 0 then ismin (div (i-1) 2)
in ()
(* Useful lemmas for proving invariants, most of the work will be here *)
predicate substitution (a1 a2: V.t elt) (i : int63) =
length a1.view = length a2.view /\
0 <= i < length a1.view /\
(forall k. 0 <= k < length a1.view -> k <> i -> a1.view[k] = a2.view[k])
lemma sub_occ_1: forall a1 a2 : V.t elt, i : int63, x : elt.
substitution a1 a2 i ->
(x <> a1.view[i] /\ x <> a2.view[i]) ->
numocc' a1 x = numocc' a2 x
lemma sub_occ_2: forall a1 a2 i.
substitution a1 a2 i -> a1.view[i] <> a2.view[i] ->
numocc' a1 a1.view[i]
= numocc' a2 a1.view[i] + 1
by let x = a1.view[i] in
numocc' a1 x = numocc a1 x 0 i + 1 + numocc a1 x (i+1) (length a1.view)
&& numocc' a2 x = numocc a2 x 0 i + 0 + numocc a2 x (i+1) (length a2.view)
lemma sub_occ_3: forall a1 a2 i.
substitution a1 a2 i -> a1.view[i] = a2.view[i] ->
numocc' a1 a1.view[i]
= numocc' a2 a1.view[i]
let lemma sub_order (a1 a2 : V.t elt) (i : int63) =
requires {substitution a1 a2 i}
requires { heap_order a1 }
requires { i > 0 -> X.cmp a1.view[div (i - 1) 2] a2.view[i] <= 0 }
requires { 2*i + 1 < length a1.view -> X.cmp a1.view[2*i + 1] a2.view[i] >= 0 }
requires { 2*i + 2 < length a1.view -> X.cmp a1.view[2*i + 2] a2.view[i] >= 0 }
ensures { heap_order a2 }
()
predicate pop (a1 a2: V.t elt) =
length a2.view = length a1.view - 1 /\
forall i. 0 <= i < length a2.view -> a1.view[i] = a2.view[i]
lemma pop_occ_1: forall a1 a2 x.
pop a1 a2 -> x <> a1.view[length a1.view - 1] ->
numocc' a1 x = numocc' a2 x
lemma pop_occ_2: forall a1 a2.
pop a1 a2 ->
numocc' a1 a1.view[length a1.view - 1] - 1
= numocc' a2 a1.view[length a1.view - 1]
lemma pop_order: forall a1 a2. pop a1 a2 -> heap_order a1 -> heap_order a2
predicate push (a1 a2 : V.t elt ) =
length a2.view = length a1.view + 1 /\
forall i. 0 <= i < length a1.view -> a1.view[i] = a2.view[i]
let ghost push_occ (a1 a2 : V.t elt) =
requires { push a1 a2 }
ensures { forall x. x <> a2.view[length a2.view - 1] -> numocc' a1 x = numocc' a2 x }
ensures { numocc' a1 a2.view[length a2.view - 1] + 1 = numocc' a2 a2.view[length a2.view - 1] }
()
let ghost push_order (a1 a2 : V.t elt) =
requires { length a1.view > 0 }
requires { push a1 a2 }
requires { le a2.view[div (length a1.view - 1) 2] a2.view[length a1.view] }
requires { heap_order a1 }
ensures { heap_order a2 }
()
lemma same_occ: forall b: bag elt, x a.
x <> a -> nb_occ x (Bag.diff b (Bag.singleton a)) = nb_occ x b
let ghost ancestor (a : V.t elt) (i : int) =
requires { heap_order a }
requires { 0 <= i < length a.view }
ensures { i > 0 -> le a.view[div (i-1) 2] a.view[i] }
()
let ghost children (a : V.t elt) (i : int) =
requires { heap_order a }
requires { 0 <= i < length a.view }
ensures { 2*i+1 < length a.view -> le a.view[i] a.view[2*i+1] }
ensures { 2*i+2 < length a.view -> le a.view[i] a.view[2*i+2] }
()
let ghost trans (a : V.t elt) (i : int) =
requires { 0 <= i < length a.view }
requires { heap_order a }
ensures {i > 0 /\ 2*i+1 < length a.view -> le a.view[div (i-1) 2] a.view[2*i+1] }
ensures {i > 0 /\ 2*i+2 < length a.view -> le a.view[div (i-1) 2] a.view[2*i+2] }
ancestor a i;
children a i;
()
let create () : heap
ensures { result.bag = empty_bag }
= { a = V.create (Some (0 : int63)) X.dummy; bag = empty_bag }
let is_empty (h: heap) : bool
ensures { result <-> h.bag = empty_bag }
= V.is_empty h.a
let size (h: heap) : int63
ensures { result = card h.bag }
= V.length h.a
exception Empty
let find_min_exn (h: heap) : elt
raises { Empty -> card h.bag = 0 }
ensures { card h.bag > 0 /\ result = minimum h }
= if V.is_empty h.a then raise Empty;
V.get h.a 0
let find_min (h: heap) : option elt
ensures { match result with
| None -> card h.bag = 0
| Some x -> card h.bag > 0 && x = minimum h end }
= if V.is_empty h.a then None else Some (V.get h.a 0)
let rec move_down (a : V.t elt) (i : int63) (x : elt) : unit
requires { heap_order a }
requires { 0 <= i < length a.view }
requires { i > 0 -> le a.view[div (i - 1) 2] x }
ensures { heap_order a }
ensures { forall e. e <> (old a).view[i] -> e <> x ->
numocc' a e = numocc' (old a) e }
ensures { let o = (old a).view[i] in
if x = o then numocc' a x = numocc' (old a) x
else
numocc' a x = numocc' (old a) x + 1 /\
numocc' a o = numocc' (old a) o - 1 }
ensures { length a.view = length (old a).view }
(* Needed for delete_min *)
variant { length a.view - i }
= let n = V.length a in
let q = if n=1 then -1 else (n-2)/2 in
if i <= q then begin (* 2i+1 exists *)
let j =
let j = 2 * i + 1 in
if j + 1 < n && X.compare (V.get a (j+1)) (V.get a j) < 0 then
j+1
else j in
ancestor a (to_int i);
children a (to_int i);
let olda = pure {a} in
if X.compare (V.get a j) x < 0 then begin
V.set a i (V.get a j);
assert { substitution (old a) a i };
sub_order olda a i;
assert { heap_order a };
move_down a j x
end else begin
V.set a i x;
assert { substitution (old a) a i };
sub_order olda a i
end
end else begin (* 2i+1 outside *)
V.set a i x;
assert { substitution (old a) a i };
end
let extract_min_exn (h: heap) : elt
raises { Empty -> card h.bag = 0 /\ h.bag = old h.bag }
ensures { result = minimum (old h) }
ensures { (old h).bag = add result h.bag }
(* Property about order is guaranteed by the invariant *)
= try
let x = V.pop h.a in
let n = V.length h.a in
if n <> 0 then begin
let min = V.get h.a 0 in
assert { min = minimum (old h) };
assert { card (Bag.diff h.bag (Bag.singleton min)) = card h.bag - 1 };
move_down h.a 0 x;
h.bag <- Bag.diff h.bag (Bag.singleton min);
min
end else begin
h.bag <- empty_bag;
x
end
with V.Empty -> raise Empty end
let delete_min_exn (h: heap) : unit
raises { Empty -> card h.bag = 0 /\ h.bag = old h.bag }
ensures { (old h).bag = add (minimum (old h)) h.bag }
= let _ = extract_min_exn h in
()
let rec move_up (a : V.t elt) (i : int63) (x : elt)
requires { heap_order a }
requires { 0 <= i < length a.view }
requires { 0 <= 2*i + 1 < length a.view -> le x a.view[2*i + 1] }
requires { 0 <= 2*i + 2 < length a.view -> le x a.view[2*i + 2] }
variant { to_int i }
ensures { heap_order a }
ensures { forall e. e <> (old a).view[i] -> e <> x -> numocc' a e = numocc' (old a) e }
ensures { let o = (old a).view[i] in
if x = o then
numocc' a x = numocc' (old a) x
else
numocc' a x = numocc' (old a) x + 1 /\
numocc' a o = numocc' (old a) o - 1 }
ensures { length a.view = length (old a).view } (* Needed for add *)
= let ghost olda = pure { a } in
if i = 0 then begin
V.set a i x;
assert { substitution olda a i };
sub_order olda a i
end
else begin
let j = (i-1)/2 in
let y = V.get a j in
children a (to_int j);
children a (to_int i);
if X.compare y x > 0 then begin
V.set a i y;
assert { substitution olda a i };
trans olda (to_int i);
sub_order olda a i;
move_up a j x
end
else begin
V.set a i x;
assert { substitution olda a i };
sub_order olda a i
end
end
let insert (x : elt) (h: heap) : unit
raises { Invalid_argument _ -> card h.bag >= Sys.max_array_length }
ensures { h.bag = add x (old h).bag }
= if size h = Sys.max_array_length then raise Invalid_argument "insert";
let n = V.length h.a in
let ghost olda = pure { h.a } in
if n = 0 then
V.push h.a x
else begin
let j = (n-1)/2 in
let y = V.get h.a j in
children h.a (to_int j);
if X.compare y x > 0 then begin
V.push h.a y;
push_order olda h.a;
push_occ olda h.a;
assert { numocc' h.a x = numocc' olda x };
move_up h.a j x
end
else begin
V.push h.a x;
push_occ olda h.a;
push_order olda h.a
end
end;
assert { numocc' h.a x = numocc' olda x + 1 };
h.bag <- add x h.bag;
end
end