Theory Dijkstra

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

section ‹Dijkstra's algorithm for shortest paths›

theory Dijkstra
  imports Mapping_Str Arrays_Ex
begin

text ‹
  Verification of Dijkstra's algorithm: function part.

  The algorithm is also verified by Nordhoff and Lammich in
  cite"Dijkstra_Shortest_Path-AFP".
›

subsection ‹Graphs›

datatype graph = Graph "nat list list"

fun size :: "graph  nat" where
  "size (Graph G) = length G"

fun weight :: "graph  nat  nat  nat" where
  "weight (Graph G) m n = (G ! m) ! n"

fun valid_graph :: "graph  bool" where
  "valid_graph (Graph G)  (i<length G. length (G ! i) = length G)"
setup add_rewrite_rule @{thm valid_graph.simps}

subsection ‹Paths on graphs›

text ‹The set of vertices less than n.›
definition verts :: "graph  nat set" where
  "verts G = {i. i < size G}"

lemma verts_mem [rewrite]: "i  verts G  i < size G" by (simp add: verts_def)
lemma card_verts [rewrite]: "card (verts G) = size G" using verts_def by auto
lemma finite_verts [forward]: "finite (verts G)" using verts_def by auto

definition is_path :: "graph  nat list  bool" where [rewrite]:
  "is_path G p  p  []  set p  verts G"

lemma is_path_to_in_verts [forward]: "is_path G p  hd p  verts G  last p  verts G"
@proof @have "last p  set p" @qed

definition joinable :: "graph  nat list  nat list  bool" where [rewrite]:
  "joinable G p q  (is_path G p  is_path G q  last p = hd q)"

definition path_join :: "graph  nat list  nat list  nat list" where [rewrite]:
  "path_join G p q = p @ tl q"
setup register_wellform_data ("path_join G p q", ["joinable G p q"])
setup add_prfstep_check_req ("path_join G p q", "joinable G p q")

lemma path_join_is_path:
  "joinable G p q  is_path G (path_join G p q)"
@proof @have "q = hd q # tl q" @qed
setup add_forward_prfstep_cond @{thm path_join_is_path} [with_term "path_join ?G ?p ?q"]

fun path_weight :: "graph  nat list  nat" where
  "path_weight G [] = 0"
| "path_weight G (x # xs) = (if xs = [] then 0 else weight G x (hd xs) + path_weight G xs)"
setup fold add_rewrite_rule @{thms path_weight.simps}

lemma path_weight_singleton [rewrite]: "path_weight G [x] = 0" by auto2
lemma path_weight_doubleton [rewrite]: "path_weight G [m, n] = weight G m n" by auto2

lemma path_weight_sum [rewrite]:
  "joinable G p q  path_weight G (path_join G p q) = path_weight G p + path_weight G q"
@proof @induct p @qed

fun path_set :: "graph  nat  nat  nat list set" where
  "path_set G m n = {p. is_path G p  hd p = m  last p = n}"

lemma path_set_mem [rewrite]:
  "p  path_set G m n  is_path G p  hd p = m  last p = n" by simp

lemma path_join_set: "joinable G p q  path_join G p q  path_set G (hd p) (last q)"
@proof @have "q = hd q # tl q" @case "tl q = []" @qed
setup add_forward_prfstep_cond @{thm path_join_set} [with_term "path_join ?G ?p ?q"]

subsection ‹Shortest paths›

definition is_shortest_path :: "graph  nat  nat  nat list  bool" where [rewrite]:
  "is_shortest_path G m n p 
     (p  path_set G m n  (p'path_set G m n. path_weight G p'  path_weight G p))"

lemma is_shortest_pathD1 [forward]:
  "is_shortest_path G m n p  p  path_set G m n" by auto2

lemma is_shortest_pathD2 [forward]:
  "is_shortest_path G m n p  p'  path_set G m n  path_weight G p'  path_weight G p" by auto2
setup del_prfstep_thm_eqforward @{thm is_shortest_path_def}

definition has_dist :: "graph  nat  nat  bool" where [rewrite]:
  "has_dist G m n  (p. is_shortest_path G m n p)"

lemma has_distI [forward]: "is_shortest_path G m n p  has_dist G m n" by auto2
lemma has_distD [resolve]: "has_dist G m n  p. is_shortest_path G m n p" by auto2
lemma has_dist_to_in_verts [forward]: "has_dist G u v  u  verts G  v  verts G" by auto2
setup del_prfstep_thm @{thm has_dist_def}

definition dist :: "graph  nat  nat  nat" where [rewrite]:
  "dist G m n = path_weight G (SOME p. is_shortest_path G m n p)"
setup register_wellform_data ("dist G m n", ["has_dist G m n"])

lemma dist_eq [rewrite]:
  "is_shortest_path G m n p  dist G m n = path_weight G p" by auto2

lemma distD [forward]:
  "has_dist G m n  p  path_set G m n  path_weight G p  dist G m n" by auto2
setup del_prfstep_thm @{thm dist_def}

lemma shortest_init [resolve]: "n  verts G  is_shortest_path G n n [n]" by auto2

subsection ‹Interior points›

text ‹List of interior points›
definition int_pts :: "nat list  nat set" where [rewrite]:
  "int_pts p = set (butlast p)"

lemma int_pts_singleton [rewrite]: "int_pts [x] = {}" by auto2
lemma int_pts_doubleton [rewrite]: "int_pts [x, y] = {x}" by auto2

definition path_set_on :: "graph  nat  nat  nat set  nat list set" where
  "path_set_on G m n V = {p. p  path_set G m n  int_pts p  V}"

lemma path_set_on_mem [rewrite]:
  "p  path_set_on G m n V  p  path_set G m n  int_pts p  V" by (simp add: path_set_on_def)

text ‹Version of shortest path on a set of points›
definition is_shortest_path_on :: "graph  nat  nat  nat list  nat set  bool" where [rewrite]:
  "is_shortest_path_on G m n p V 
    (p  path_set_on G m n V  (p'path_set_on G m n V. path_weight G p'  path_weight G p))"

lemma is_shortest_path_onD1 [forward]:
  "is_shortest_path_on G m n p V  p  path_set_on G m n V" by auto2

lemma is_shortest_path_onD2 [forward]:
  "is_shortest_path_on G m n p V  p'  path_set_on G m n V  path_weight G p'  path_weight G p" by auto2
setup del_prfstep_thm_eqforward @{thm is_shortest_path_on_def}

definition has_dist_on :: "graph  nat  nat  nat set  bool" where [rewrite]:
  "has_dist_on G m n V  (p. is_shortest_path_on G m n p V)"

lemma has_dist_onI [forward]: "is_shortest_path_on G m n p V  has_dist_on G m n V" by auto2
lemma has_dist_onD [resolve]: "has_dist_on G m n V  p. is_shortest_path_on G m n p V" by auto2
setup del_prfstep_thm @{thm has_dist_on_def}

definition dist_on :: "graph  nat  nat  nat set  nat" where [rewrite]:
  "dist_on G m n V = path_weight G (SOME p. is_shortest_path_on G m n p V)"
setup register_wellform_data ("dist_on G m n V", ["has_dist_on G m n V"])

lemma dist_on_eq [rewrite]:
  "is_shortest_path_on G m n p V  dist_on G m n V = path_weight G p" by auto2

lemma dist_onD [forward]:
  "has_dist_on G m n V  p  path_set_on G m n V  path_weight G p  dist_on G m n V" by auto2
setup del_prfstep_thm @{thm dist_on_def}

subsection ‹Two splitting lemmas›

lemma path_split1 [backward]: "is_path G p  hd p  V  last p  V 
  p1 p2. joinable G p1 p2  p = path_join G p1 p2  int_pts p1  V  hd p2  V"
@proof @induct p @with
  @subgoal "p = a # p'"
    @let "p = a # p'"
    @case "p' = []"
    @case "hd p'  V" @with @have "p = path_join G [a, hd p'] p'" @end
    @obtain p1 p2 where "joinable G p1 p2" "p' = path_join G p1 p2" "int_pts p1  V" "hd p2  V"
    @have "p = path_join G (a # p1) p2"
  @endgoal @end
@qed

lemma path_split2 [backward]: "is_path G p  hd p  last p 
  q n. joinable G q [n, last p]  p = path_join G q [n, last p]"
@proof
  @have "p = butlast p @ [last p]"
  @have "butlast p  []"
  @let "n = last (butlast p)"
  @have "p = path_join G (butlast p) [n, last p]"
@qed

subsection ‹Deriving has\_dist and has\_dist\_on›

definition known_dists :: "graph  nat set  bool" where [rewrite]:
  "known_dists G V  (V  verts G  0  V 
      (iverts G. has_dist_on G 0 i V) 
      (iV. has_dist G 0 i  dist G 0 i = dist_on G 0 i V))"

lemma derive_dist [backward2]:
  "known_dists G V 
   m  verts G - V 
   iverts G - V. dist_on G 0 i V  dist_on G 0 m V 
   has_dist G 0 m  dist G 0 m = dist_on G 0 m V"
@proof
  @obtain p where "is_shortest_path_on G 0 m p V"
  @have "is_shortest_path G 0 m p" @with
    @have "p  path_set G 0 m"
    @have "p'path_set G 0 m. path_weight G p'  path_weight G p" @with
      @obtain p1 p2 where "joinable G p1 p2" "p' = path_join G p1 p2"
                          "int_pts p1  V" "hd p2  V"
      @let "x = last p1"
      @have "dist_on G 0 x V  dist_on G 0 m V"
      @have "p1  path_set_on G 0 x V"
      @have "path_weight G p1  dist_on G 0 x V"
      @have "path_weight G p'  dist_on G 0 m V + path_weight G p2"
    @end
  @end
@qed

lemma join_def' [resolve]: "joinable G p q  path_join G p q = butlast p @ q"
@proof
  @have "p = butlast p @ [last p]"
  @have "path_join G p q = butlast p @ [last p] @ tl q"
@qed

lemma int_pts_join [rewrite]:
  "joinable G p q  int_pts (path_join G p q) = int_pts p  int_pts q"
@proof @have "path_join G p q = butlast p @ q" @qed

lemma dist_on_triangle_ineq [backward]:
  "has_dist_on G k m V  has_dist_on G k n V  V  verts G  n  verts G  m  V 
   dist_on G k m V + weight G m n  dist_on G k n V"
@proof
  @obtain p where "is_shortest_path_on G k m p V"
  @let "pq = path_join G p [m, n]"
  @have "V  {m} = V"
  @have "pq  path_set_on G k n V"
@qed

lemma derive_dist_on [backward2]:
  "known_dists G V 
   m  verts G - V 
   iverts G - V. dist_on G 0 i V  dist_on G 0 m V 
   V' = V  {m} 
   n  verts G - V' 
   has_dist_on G 0 n V'  dist_on G 0 n V' = min (dist_on G 0 n V) (dist_on G 0 m V + weight G m n)"
@proof
  @have "has_dist G 0 m  dist G 0 m = dist_on G 0 m V"
  @let "M = min (dist_on G 0 n V) (dist_on G 0 m V + weight G m n)"
  @have "ppath_set_on G 0 n V'. path_weight G p  M" @with
    @obtain q n' where "joinable G q [n', n]" "p = path_join G q [n', n]"
    @have "q  path_set G 0 n'"
    @have "n'  V'"
    @case "n'  V" @with
      @have "dist_on G 0 n' V = dist G 0 n'"
      @have "path_weight G q  dist_on G 0 n' V"
      @have "path_weight G p  dist_on G 0 n' V + weight G n' n"
      @have "dist_on G 0 n' V + weight G n' n  dist_on G 0 n V"
    @end
    @have "n' = m"
    @have "path_weight G q  dist G 0 m"
    @have "path_weight G p  dist G 0 m + weight G m n"
  @end
  @case "dist_on G 0 m V + weight G m n  dist_on G 0 n V" @with
    @obtain p where "is_shortest_path_on G 0 n p V"
    @have "is_shortest_path_on G 0 n p V'" @with
      @have "p  path_set_on G 0 n V'" @with @have "V  V'" @end
    @end
  @end
  @have "M = dist_on G 0 m V + weight G m n"
  @obtain pm where "is_shortest_path_on G 0 m pm V"
  @have "path_weight G pm = dist G 0 m"
  @let "p = path_join G pm [m, n]"
  @have "joinable G pm [m, n]"
  @have "path_weight G p = path_weight G pm + weight G m n"
  @have "is_shortest_path_on G 0 n p V'"
@qed

subsection ‹Invariant for the Dijkstra's algorithm›

text ‹The state consists of an array maintaining the best estimates,
  and a heap containing estimates for the unknown vertices.›
datatype state = State (est: "nat list") (heap: "(nat, nat) map")
setup add_simple_datatype "state"

definition unknown_set :: "state  nat set" where [rewrite]:
  "unknown_set S = keys_of (heap S)"

definition known_set :: "state  nat set" where [rewrite]:
  "known_set S = {..<length (est S)} - unknown_set S"

text ‹Invariant: for every vertex, the estimate is at least the shortest distance.
  Furthermore, for the known vertices the estimate is exact.›
definition inv :: "graph  state  bool" where [rewrite]:
  "inv G S  (let V = known_set S; W = unknown_set S; M = heap S in
      (length (est S) = size G  known_dists G V 
      keys_of M  verts G 
      (iW. Mi = Some (est S ! i)) 
      (iV. est S ! i = dist G 0 i) 
      (iverts G. est S ! i = dist_on G 0 i V)))"

lemma invE1 [forward]: "inv G S  length (est S) = size G  known_dists G (known_set S)  unknown_set S  verts G" by auto2
lemma invE2 [forward]: "inv G S  i  known_set S  est S ! i = dist G 0 i" by auto2
lemma invE3 [forward]: "inv G S  i  verts G  est S ! i = dist_on G 0 i (known_set S)" by auto2
lemma invE4 [rewrite]: "inv G S  i  unknown_set S  (heap S)i = Some (est S ! i)" by auto2
setup del_prfstep_thm_str "@eqforward" @{thm inv_def}

lemma inv_unknown_set [rewrite]:
  "inv G S  unknown_set S = verts G - known_set S" by auto2

lemma dijkstra_end_inv [forward]:
  "inv G S  unknown_set S = {}  iverts G. has_dist G 0 i  est S ! i = dist G 0 i" by auto2

subsection ‹Starting state›

definition dijkstra_start_state :: "graph  state" where [rewrite]:
  "dijkstra_start_state G =
     State (list (λi. if i = 0 then 0 else weight G 0 i) (size G))
           (map_constr (λi. i > 0) (λi. weight G 0 i) (size G))"
setup register_wellform_data ("dijkstra_start_state G", ["size G > 0"])

lemma dijkstra_start_known_set [rewrite]:
  "size G > 0  known_set (dijkstra_start_state G) = {0}" by auto2
    
lemma dijkstra_start_unknown_set [rewrite]:
  "size G > 0  unknown_set (dijkstra_start_state G) = verts G - {0}" by auto2

lemma card_start_state [rewrite]:
  "size G > 0  card (unknown_set (dijkstra_start_state G)) = size G - 1"
@proof @have "0  verts G" @qed

text ‹Starting start of Dijkstra's algorithm satisfies the invariant.›
theorem dijkstra_start_inv [backward]:
  "size G > 0  inv G (dijkstra_start_state G)"
@proof
  @let "V = {0::nat}"
  @have "has_dist G 0 0  dist G 0 0 = 0" @with
    @have "is_shortest_path G 0 0 [0]" @end
  @have "has_dist_on G 0 0 V  dist_on G 0 0 V = 0" @with
    @have "is_shortest_path_on G 0 0 [0] V" @end
  @have "V  verts G  0  V"
  @have (@rule) "iverts G. i  0  has_dist_on G 0 i V  dist_on G 0 i V = weight G 0 i" @with
    @let "p = [0, i]"
    @have "is_shortest_path_on G 0 i p V" @with
      @have "p  path_set_on G 0 i V"
      @have "p'path_set_on G 0 i V. path_weight G p'  weight G 0 i" @with
        @obtain q n where "joinable G q [n, last p']" "p' = path_join G q [n, last p']"
        @have "n  V" @have "n = 0"
        @have "path_weight G p' = path_weight G q + weight G 0 i"
      @end
    @end
  @end
@qed

subsection ‹Step of Dijkstra's algorithm›

fun dijkstra_step :: "graph  nat  state  state" where
  "dijkstra_step G m (State e M) =
    (let M' = delete_map m M;
         e' = list_update_set (λi. i  keys_of M') (λi. min (e ! m + weight G m i) (e ! i)) e;
         M'' = map_update_all (λi. e' ! i) M'
     in State e' M'')"
setup add_rewrite_rule @{thm dijkstra_step.simps}
setup register_wellform_data ("dijkstra_step G m S", ["inv G S", "m ∈ unknown_set S"])

lemma has_dist_on_larger [backward1]:
  "has_dist G m n  has_dist_on G m n V  dist_on G m n V = dist G m n 
   has_dist_on G m n (V  {x})  dist_on G m n (V  {x}) = dist G m n"
@proof
  @obtain p where "is_shortest_path_on G m n p V"
  @let "V' = V  {x}"
  @have "p  path_set_on G m n V'" @with @have "V  V'" @end
  @have "is_shortest_path_on G m n p V'"
@qed

lemma dijkstra_step_unknown_set [rewrite]:
  "inv G S  m  unknown_set S  unknown_set (dijkstra_step G m S) = unknown_set S - {m}" by auto2

lemma dijkstra_step_known_set [rewrite]:
  "inv G S  m  unknown_set S  known_set (dijkstra_step G m S) = known_set S  {m}" by auto2

text ‹One step of Dijkstra's algorithm preserves the invariant.›
theorem dijkstra_step_preserves_inv [backward]:
  "inv G S  is_heap_min m (heap S)  inv G (dijkstra_step G m S)"
@proof
  @let "V = known_set S" "V' = V  {m}"
  @have (@rule) "iV. has_dist G 0 i  has_dist_on G 0 i V'  dist_on G 0 i V' = dist G 0 i"
  @have "has_dist G 0 m  dist G 0 m = dist_on G 0 m V"
  @have "has_dist_on G 0 m V'  dist_on G 0 m V' = dist G 0 m"
  @have (@rule) "iverts G - V'. has_dist_on G 0 i V'  dist_on G 0 i V' = min (dist_on G 0 i V) (dist_on G 0 m V + weight G m i)"
  @let "S' = dijkstra_step G m S"
  @have "known_dists G V'"
  @have "iV'. est S' ! i = dist G 0 i"
  @have "iverts G. est S' ! i = dist_on G 0 i V'" @with @case "i  V'" @end
@qed

definition is_dijkstra_step :: "graph  state  state  bool" where [rewrite]:
  "is_dijkstra_step G S S'  (m. is_heap_min m (heap S)  S' = dijkstra_step G m S)"

lemma is_dijkstra_stepI [backward2]:
  "is_heap_min m (heap S)  dijkstra_step G m S = S'  is_dijkstra_step G S S'" by auto2

lemma is_dijkstra_stepD1 [forward]:
  "inv G S  is_dijkstra_step G S S'  inv G S'" by auto2

lemma is_dijkstra_stepD2 [forward]:
  "inv G S  is_dijkstra_step G S S'  card (unknown_set S') = card (unknown_set S) - 1" by auto2
setup del_prfstep_thm @{thm is_dijkstra_step_def}

end