Theory Indexed_PQueue

(*
  File: Indexed_PQueue.thy
  Author: Bohua Zhan
*)

section ‹Indexed priority queues›

theory Indexed_PQueue
  imports Arrays_Ex Mapping_Str
begin

text ‹
  Verification of indexed priority queue: functional part. The data
  structure is also verified by Lammich in
  cite"Refine_Imperative_HOL-AFP".
›

subsection ‹Successor functions, eq-pred predicate›

fun s1 :: "nat  nat" where "s1 m = 2 * m + 1"
fun s2 :: "nat  nat" where "s2 m = 2 * m + 2"

lemma s_inj [forward]:
  "s1 m = s1 m'  m = m'" "s2 m = s2 m'  m = m'" by auto
lemma s_neq [resolve]:
  "s1 m  s2 m'" "s1 m > m" "s2 m > m" "s2 m > s1 m" using s1.simps s2.simps by presburger+
setup add_forward_prfstep_cond @{thm s_neq(2)} [with_term "s1 ?m"]
setup add_forward_prfstep_cond @{thm s_neq(3)} [with_term "s2 ?m"]
setup add_forward_prfstep_cond @{thm s_neq(4)} [with_term "s2 ?m", with_term "s1 ?m"]

inductive eq_pred :: "nat  nat  bool" where
  "eq_pred n n"
| "eq_pred n m  eq_pred n (s1 m)"
| "eq_pred n m  eq_pred n (s2 m)"
setup add_case_induct_rule @{thm eq_pred.cases}
setup add_prop_induct_rule @{thm eq_pred.induct}
setup add_resolve_prfstep @{thm eq_pred.intros(1)}
setup fold add_backward_prfstep @{thms eq_pred.intros(2,3)}

lemma eq_pred_parent1 [forward]:
  "eq_pred i (s1 k)  i  s1 k  eq_pred i k"
@proof @let "v = s1 k" @prop_induct "eq_pred i v" @qed

lemma eq_pred_parent2 [forward]:
  "eq_pred i (s2 k)  i  s2 k  eq_pred i k"
@proof @let "v = s2 k" @prop_induct "eq_pred i v" @qed

lemma eq_pred_cases:
  "eq_pred i j  eq_pred (s1 i) j  eq_pred (s2 i) j  j = i  j = s1 i  j = s2 i"
@proof @prop_induct "eq_pred i j" @qed
setup add_forward_prfstep_cond @{thm eq_pred_cases} [with_cond "?i ≠ s1 ?k", with_cond "?i ≠ s2 ?k"]

lemma eq_pred_le [forward]: "eq_pred i j  i  j"
@proof @prop_induct "eq_pred i j" @qed

subsection ‹Heap property›

text ‹The corresponding tree is a heap›
definition is_heap :: "('a × 'b::linorder) list  bool" where [rewrite]:
  "is_heap xs = (i j. eq_pred i j  j < length xs  snd (xs ! i)  snd (xs ! j))"

lemma is_heapD:
  "is_heap xs  j < length xs  eq_pred i j  snd (xs ! i)  snd (xs ! j)" by auto2
setup add_forward_prfstep_cond @{thm is_heapD} [with_term "?xs ! ?j"]
setup del_prfstep_thm_eqforward @{thm is_heap_def}

subsection ‹Bubble-down›

text ‹The corresponding tree is a heap, except k is not necessarily smaller than its descendents.›
definition is_heap_partial1 :: "('a × 'b::linorder) list  nat  bool" where [rewrite]:
  "is_heap_partial1 xs k = (i j. eq_pred i j  i  k  j < length xs  snd (xs ! i)  snd (xs ! j))"

text ‹Two cases of switching with s1 k.›
lemma bubble_down1:
  "s1 k < length xs  is_heap_partial1 xs k  snd (xs ! k) > snd (xs ! s1 k) 
   snd (xs ! s1 k)  snd (xs ! s2 k)  is_heap_partial1 (list_swap xs k (s1 k)) (s1 k)" by auto2
setup add_forward_prfstep_cond @{thm bubble_down1} [with_term "list_swap ?xs ?k (s1 ?k)"]

lemma bubble_down2:
  "s1 k < length xs  is_heap_partial1 xs k  snd (xs ! k) > snd (xs ! s1 k) 
   s2 k  length xs  is_heap_partial1 (list_swap xs k (s1 k)) (s1 k)" by auto2
setup add_forward_prfstep_cond @{thm bubble_down2} [with_term "list_swap ?xs ?k (s1 ?k)"]

text ‹One case of switching with s2 k.›
lemma bubble_down3:
  "s2 k < length xs  is_heap_partial1 xs k  snd (xs ! s1 k) > snd (xs ! s2 k) 
   snd (xs ! k) > snd (xs ! s2 k)  xs' = list_swap xs k (s2 k)  is_heap_partial1 xs' (s2 k)" by auto2
setup add_forward_prfstep_cond @{thm bubble_down3} [with_term "?xs'"]

subsection ‹Bubble-up›

fun par :: "nat  nat" where
  "par m = (m - 1) div 2"
setup register_wellform_data ("par m", ["m ≠ 0"])

lemma ps_inverse [rewrite]: "par (s1 k) = k" "par (s2 k) = k" by simp+

lemma p_basic: "m  0  par m < m" by auto
setup add_forward_prfstep_cond @{thm p_basic} [with_term "par ?m"]

lemma p_cases: "m  0  m = s1 (par m)  m = s2 (par m)" by auto
setup add_forward_prfstep_cond @{thm p_cases} [with_term "par ?m"]

lemma eq_pred_p_next:
  "i  0  eq_pred i j  eq_pred (par i) j"
@proof @prop_induct "eq_pred i j" @qed
setup add_forward_prfstep_cond @{thm eq_pred_p_next} [with_term "par ?i"]

lemma heap_implies_hd_min [resolve]:
  "is_heap xs  i < length xs  xs  []  snd (hd xs)  snd (xs ! i)"
@proof
  @strong_induct i
  @case "i = 0" @apply_induct_hyp "par i"
  @have "eq_pred (par i) i"
@qed

text ‹The corresponding tree is a heap, except k is not necessarily greater than its ancestors.›
definition is_heap_partial2 :: "('a × 'b::linorder) list  nat  bool" where [rewrite]:
  "is_heap_partial2 xs k = (i j. eq_pred i j  j < length xs  j  k  snd (xs ! i)  snd (xs ! j))"

lemma bubble_up1 [forward]:
  "k < length xs  is_heap_partial2 xs k  snd (xs ! k) < snd (xs ! par k)  k  0 
   is_heap_partial2 (list_swap xs k (par k)) (par k)" by auto2

lemma bubble_up2 [forward]:
  "k < length xs  is_heap_partial2 xs k  snd (xs ! k)  snd (xs ! par k)  k  0 
   is_heap xs" by auto2
setup del_prfstep_thm @{thm p_cases}

subsection ‹Indexed priority queue›

type_synonym 'a idx_pqueue = "(nat × 'a) list × nat option list"

fun index_of_pqueue :: "'a idx_pqueue  bool" where
  "index_of_pqueue (xs, m) = (
    (i<length xs. fst (xs ! i) < length m  m ! (fst (xs ! i)) = Some i) 
    (i. k<length m. m ! k = Some i  i < length xs  fst (xs ! i) = k))"
setup add_rewrite_rule @{thm index_of_pqueue.simps}

lemma index_of_pqueueD1:
  "i < length xs  index_of_pqueue (xs, m) 
   fst (xs ! i) < length m  m ! (fst (xs ! i)) = Some i" by auto2
setup add_forward_prfstep_cond @{thm index_of_pqueueD1} [with_term "?xs ! ?i"]

lemma index_of_pqueueD2 [forward]:
  "k < length m  index_of_pqueue (xs, m) 
   m ! k = Some i  i < length xs  fst (xs ! i) = k" by auto2

lemma index_of_pqueueD3 [forward]:
  "index_of_pqueue (xs, m)  p  set xs  fst p < length m"
@proof @obtain i where "i < length xs" "xs ! i = p" @qed
setup del_prfstep_thm_eqforward @{thm index_of_pqueue.simps}

lemma has_index_unique_key [forward]:
  "index_of_pqueue (xs, m)  unique_keys_set (set xs)"
@proof
  @have "a x y. (a, x)  set xs  (a, y)  set xs  x = y" @with
    @obtain i where "i < length xs" "xs ! i = (a, x)"
    @obtain j where "j < length xs" "xs ! j = (a, y)"
  @end
@qed

lemma has_index_keys_of [rewrite]:
  "index_of_pqueue (xs, m)  has_key_alist xs k  (k < length m  m ! k  None)"
@proof
  @case "has_key_alist xs k" @with
    @obtain v' where "(k, v')  set xs"
    @obtain i where "i < length xs  xs ! i = (k, v')"
  @end
@qed

lemma has_index_distinct [forward]:
  "index_of_pqueue (xs, m)  distinct xs"
@proof
  @have "i<length xs. j<length xs. i  j  xs ! i  xs ! j"
@qed

subsection ‹Basic operations on indexed\_queue›

fun idx_pqueue_swap_fun :: "(nat × 'a) list × nat option list  nat  nat  (nat × 'a) list × nat option list" where
  "idx_pqueue_swap_fun (xs, m) i j = (
    list_swap xs i j, ((m [fst (xs ! i) := Some j]) [fst (xs ! j) := Some i]))"

lemma index_of_pqueue_swap [forward_arg]:
  "i < length xs  j < length xs  index_of_pqueue (xs, m) 
   index_of_pqueue (idx_pqueue_swap_fun (xs, m) i j)"
@proof @unfold "idx_pqueue_swap_fun (xs, m) i j" @qed

lemma fst_idx_pqueue_swap [rewrite]:
  "fst (idx_pqueue_swap_fun (xs, m) i j) = list_swap xs i j"
@proof @unfold "idx_pqueue_swap_fun (xs, m) i j" @qed

lemma snd_idx_pqueue_swap [rewrite]:
  "length (snd (idx_pqueue_swap_fun (xs, m) i j)) = length m"
@proof @unfold "idx_pqueue_swap_fun (xs, m) i j" @qed

fun idx_pqueue_push_fun :: "nat  'a  'a idx_pqueue  'a idx_pqueue" where
  "idx_pqueue_push_fun k v (xs, m) = (xs @ [(k, v)], list_update m k (Some (length xs)))"

lemma idx_pqueue_push_correct [forward_arg]:
  "index_of_pqueue (xs, m)  k < length m  ¬has_key_alist xs k 
   r = idx_pqueue_push_fun k v (xs, m) 
   index_of_pqueue r  fst r = xs @ [(k, v)]  length (snd r) = length m"
@proof @unfold "idx_pqueue_push_fun k v (xs, m)" @qed

fun idx_pqueue_pop_fun :: "'a idx_pqueue  'a idx_pqueue" where
  "idx_pqueue_pop_fun (xs, m) = (butlast xs, list_update m (fst (last xs)) None)"

lemma idx_pqueue_pop_correct [forward_arg]:
  "index_of_pqueue (xs, m)  xs  []  r = idx_pqueue_pop_fun (xs, m) 
   index_of_pqueue r  fst r = butlast xs  length (snd r) = length m"
@proof
  @unfold "idx_pqueue_pop_fun (xs, m)"
  @have "length xs = length (butlast xs) + 1"
  @have "fst (xs ! length (butlast xs)) < length m"  (* TODO: remove? *)
@qed

subsection ‹Bubble up and down›

function idx_bubble_down_fun :: "'a::linorder idx_pqueue  nat  'a idx_pqueue" where
  "idx_bubble_down_fun (xs, m) k = (
    if s2 k < length xs then
      if snd (xs ! s1 k)  snd (xs ! s2 k) then
        if snd (xs ! k) > snd (xs ! s1 k) then
          idx_bubble_down_fun (idx_pqueue_swap_fun (xs, m) k (s1 k)) (s1 k)
        else (xs, m)
      else
        if snd (xs ! k) > snd (xs ! s2 k) then
          idx_bubble_down_fun (idx_pqueue_swap_fun (xs, m) k (s2 k)) (s2 k)
        else (xs, m)
    else if s1 k < length xs then
      if snd (xs ! k) > snd (xs ! s1 k) then
        idx_bubble_down_fun (idx_pqueue_swap_fun (xs, m) k (s1 k)) (s1 k)
      else (xs, m)
    else (xs, m))"
  by pat_completeness auto
  termination by (relation "measure (λ((xs,_),k). (length xs - k))") (simp_all, auto2+)

lemma idx_bubble_down_fun_correct:
  "r = idx_bubble_down_fun x k  is_heap_partial1 (fst x) k 
   is_heap (fst r)  mset (fst r) = mset (fst x)  length (snd r) = length (snd x)"
@proof @fun_induct "idx_bubble_down_fun x k" @with
  @subgoal "(x = (xs, m), k = k)"
  @unfold "idx_bubble_down_fun (xs, m) k"
  @case "s2 k < length xs" @with
    @case "snd (xs ! s1 k)  snd (xs ! s2 k)"
  @end
  @case "s1 k < length xs" @end
@qed
setup add_forward_prfstep_cond @{thm idx_bubble_down_fun_correct} [with_term "?r"]

lemma idx_bubble_down_fun_correct2 [forward]:
  "index_of_pqueue x  index_of_pqueue (idx_bubble_down_fun x k)"
@proof @fun_induct "idx_bubble_down_fun x k" @with
  @subgoal "(x = (xs, m), k = k)"
  @unfold "idx_bubble_down_fun (xs, m) k"
  @case "s2 k < length xs" @with
    @case "snd (xs ! s1 k)  snd (xs ! s2 k)"
  @end
  @case "s1 k < length xs" @end
@qed

fun idx_bubble_up_fun :: "'a::linorder idx_pqueue  nat  'a idx_pqueue" where
  "idx_bubble_up_fun (xs, m) k = (
    if k = 0 then (xs, m)
    else if k < length xs then
      if snd (xs ! k) < snd (xs ! par k) then
        idx_bubble_up_fun (idx_pqueue_swap_fun (xs, m) k (par k)) (par k)
      else (xs, m)
    else (xs, m))"

lemma idx_bubble_up_fun_correct:
  "r = idx_bubble_up_fun x k  is_heap_partial2 (fst x) k 
   is_heap (fst r)  mset (fst r) = mset (fst x)  length (snd r) = length (snd x)"
@proof @fun_induct "idx_bubble_up_fun x k" @with
  @subgoal "(x = (xs, m), k = k)"
  @unfold "idx_bubble_up_fun (xs, m) k" @end
@qed
setup add_forward_prfstep_cond @{thm idx_bubble_up_fun_correct} [with_term "?r"]

lemma idx_bubble_up_fun_correct2 [forward]:
  "index_of_pqueue x  index_of_pqueue (idx_bubble_up_fun x k)"
@proof @fun_induct "idx_bubble_up_fun x k" @with
  @subgoal "(x = (xs, m), k = k)"
  @unfold "idx_bubble_up_fun (xs, m) k" @end
@qed

subsection ‹Main operations›

fun delete_min_idx_pqueue_fun :: "'a::linorder idx_pqueue  (nat × 'a) × 'a idx_pqueue" where
  "delete_min_idx_pqueue_fun (xs, m) = (
    let (xs', m') = idx_pqueue_swap_fun (xs, m) 0 (length xs - 1);
        a'' = idx_pqueue_pop_fun (xs', m')
     in (last xs', idx_bubble_down_fun a'' 0))"

lemma delete_min_idx_pqueue_correct:
  "index_of_pqueue (xs, m)  xs  []  res = delete_min_idx_pqueue_fun (xs, m) 
   index_of_pqueue (snd res)"
@proof @unfold "delete_min_idx_pqueue_fun (xs, m)" @qed
setup add_forward_prfstep_cond @{thm delete_min_idx_pqueue_correct} [with_term "?res"]

lemma hd_last_swap_eval_last [rewrite]:
  "xs  []  last (list_swap xs 0 (length xs - 1)) = hd xs"
@proof
  @let "xs' = list_swap xs 0 (length xs - 1)"
  @have "last xs' = xs' ! (length xs - 1)"
@qed

text ‹Correctness of delete-min.›
theorem delete_min_idx_pqueue_correct2:
  "is_heap xs  xs  []  res = delete_min_idx_pqueue_fun (xs, m)  index_of_pqueue (xs, m) 
   is_heap (fst (snd res))  fst res = hd xs  length (snd (snd res)) = length m 
   map_of_alist (fst (snd res)) = delete_map (fst (fst res)) (map_of_alist xs)"
@proof @unfold "delete_min_idx_pqueue_fun (xs, m)"
  @let "xs' = list_swap xs 0 (length xs - 1)"
  @have "is_heap_partial1 (butlast xs') 0"
@qed
setup add_forward_prfstep_cond @{thm delete_min_idx_pqueue_correct2} [with_term "?res"]

fun insert_idx_pqueue_fun :: "nat  'a::linorder  'a idx_pqueue  'a idx_pqueue" where
  "insert_idx_pqueue_fun k v x = (
    let x' = idx_pqueue_push_fun k v x in
      idx_bubble_up_fun x' (length (fst x') - 1))"

lemma insert_idx_pqueue_correct [forward_arg]:
  "index_of_pqueue (xs, m)  k < length m  ¬has_key_alist xs k 
   index_of_pqueue (insert_idx_pqueue_fun k v (xs, m))"
@proof @unfold "insert_idx_pqueue_fun k v (xs, m)" @qed

text ‹Correctness of insertion.›
theorem insert_idx_pqueue_correct2:
  "index_of_pqueue (xs, m)  is_heap xs  k < length m  ¬has_key_alist xs k 
   r = insert_idx_pqueue_fun k v (xs, m) 
   is_heap (fst r)  length (snd r) = length m 
   map_of_alist (fst r) = map_of_alist xs { k  v }"
@proof @unfold "insert_idx_pqueue_fun k v (xs, m)"
  @have "is_heap_partial2 (xs @ [(k, v)]) (length xs)"
@qed
setup add_forward_prfstep_cond @{thm insert_idx_pqueue_correct2} [with_term "?r"]

fun update_idx_pqueue_fun :: "nat  'a::linorder  'a idx_pqueue  'a idx_pqueue" where
  "update_idx_pqueue_fun k v (xs, m) = (
    if m ! k = None then
      insert_idx_pqueue_fun k v (xs, m)
    else let
      i = the (m ! k);
      xs' = list_update xs i (k, v)
    in
      if snd (xs ! i)  v then idx_bubble_down_fun (xs', m) i
      else idx_bubble_up_fun (xs', m) i)"

lemma update_idx_pqueue_correct [forward_arg]:
  "index_of_pqueue (xs, m)  k < length m 
   index_of_pqueue (update_idx_pqueue_fun k v (xs, m))"
@proof @unfold "update_idx_pqueue_fun k v (xs, m)"
  @let "i' = the (m ! k)"
  @let "xs' = list_update xs i' (k, v)"
  @case "m ! k = None"
  @have "index_of_pqueue (xs', m)"
@qed

text ‹Correctness of update.›
theorem update_idx_pqueue_correct2:
  "index_of_pqueue (xs, m)  is_heap xs  k < length m 
   r = update_idx_pqueue_fun k v (xs, m) 
   is_heap (fst r)  length (snd r) = length m 
   map_of_alist (fst r) = map_of_alist xs { k  v }"
@proof @unfold "update_idx_pqueue_fun k v (xs, m)"
  @let "i = the (m ! k)"
  @let "xs' = list_update xs i (k, v)"
  @have "xs' = fst (xs', m)" (* TODO: remove *)
  @case "m ! k = None"
  @case "snd (xs ! the (m ! k))  v" @with
    @have "is_heap_partial1 xs' i"
  @end
  @have "is_heap_partial2 xs' i"
@qed
setup add_forward_prfstep_cond @{thm update_idx_pqueue_correct2} [with_term "?r"]

end