Theory BMSSP_Shortest_Path_Lemmas

theory BMSSP_Shortest_Path_Lemmas
  imports BMSSP_Correctness
begin

section ‹Shortest-Path Prefix Facts›

text ‹
  The algorithm's tree-based correctness arguments repeatedly use the fact that
  vertices appearing earlier on a shortest path have no larger true distance.
  This theory proves that fact from the finite non-negative weighted graph
  model, without assuming uniqueness of shortest paths.
›

context finite_weighted_digraph
begin

lemma walk_nonempty:
  assumes "walk p"
  shows "p  []"
  using assms by (cases p) auto

lemma walk_weight_nonneg:
  assumes "walk p"
  shows "0  walk_weight p"
  using assms
proof (induction p)
  case Nil
  then show ?case by simp
next
  case (Cons x ps)
  show ?case
  proof (cases ps)
    case Nil
    then show ?thesis by simp
  next
    case (Cons y ys)
    then have edge: "(x, y)  E" and tail: "walk (y # ys)"
      using Cons.prems by auto
    have wx: "0  w x y"
      using edge nonneg_weight by auto
    have tail_nonneg: "0  walk_weight (y # ys)"
      using Cons.IH tail Cons by simp
    show ?thesis
      using wx tail_nonneg Cons by simp
  qed
qed

lemma dist_nonneg:
  assumes "reachable a b"
  shows "0  dist a b"
proof -
  have "dist a b  simple_walk_weights a b"
    by (rule dist_is_simple_walk_weight[OF assms])
  then obtain p where p: "simple_walk_betw a p b"
    and dist_eq: "walk_weight p = dist a b"
    unfolding simple_walk_weights_def by auto
  have "walk p"
    using p unfolding simple_walk_betw_def walk_betw_def by blast
  then have "0  walk_weight p"
    by (rule walk_weight_nonneg)
  then show ?thesis
    using dist_eq by simp
qed

lemma walk_take_Suc:
  assumes "walk p" "i < length p"
  shows "walk (take (Suc i) p)"
  using assms
proof (induction p arbitrary: i)
  case Nil
  then show ?case by simp
next
  case (Cons x ps)
  show ?case
  proof (cases ps)
    case Nil
    then show ?thesis
      using Cons by (cases i) auto
  next
    case (Cons y ys)
    then have walk_tail: "walk (y # ys)"
      using Cons.prems by auto
    show ?thesis
    proof (cases i)
      case 0
      then show ?thesis
        using Cons.prems Cons by auto
    next
      case (Suc j)
      then have "j < length (y # ys)"
        using Cons.prems Cons by simp
      then have "walk (take (Suc j) (y # ys))"
        using Cons.IH walk_tail Cons by simp
      then show ?thesis
        using Cons.prems Cons Suc by auto
    qed
  qed
qed

lemma walk_weight_take_Suc_le:
  assumes "walk p" "i < length p"
  shows "walk_weight (take (Suc i) p)  walk_weight p"
  using assms
proof (induction p arbitrary: i)
  case Nil
  then show ?case by simp
next
  case (Cons x ps)
  show ?case
  proof (cases ps)
    case Nil
    then show ?thesis
      using Cons by (cases i) auto
  next
    case (Cons y ys)
    then have edge: "(x, y)  E" and walk_tail: "walk (y # ys)"
      using Cons.prems by auto
    show ?thesis
    proof (cases i)
      case 0
      have "0  w x y"
        using edge nonneg_weight by auto
      moreover have "0  walk_weight (y # ys)"
        using walk_weight_nonneg[OF walk_tail] .
      ultimately show ?thesis
        using Cons 0 by auto
    next
      case (Suc j)
      then have j: "j < length (y # ys)"
        using Cons.prems Cons by simp
      have "walk_weight (take (Suc j) (y # ys))  walk_weight (y # ys)"
        using Cons.IH walk_tail j Cons by simp
      then show ?thesis
        using Cons Suc by auto
    qed
  qed
qed

lemma simple_walk_prefix:
  assumes p: "simple_walk_betw a p b"
    and i: "i < length p"
  shows "simple_walk_betw a (take (Suc i) p) (p ! i)"
proof -
  have nonempty: "p  []" and hd: "hd p = a" and walk_p: "walk p" and distinct_p: "distinct p"
    using p unfolding simple_walk_betw_def walk_betw_def by auto
  have take_nonempty: "take (Suc i) p  []"
    using i by auto
  have "hd (take (Suc i) p) = a"
    using hd i by (simp add: hd_take)
  moreover have "last (take (Suc i) p) = p ! i"
  proof -
    have len_take: "length (take (Suc i) p) = Suc i"
      using i by simp
    have "last (take (Suc i) p) =
        take (Suc i) p ! (length (take (Suc i) p) - 1)"
      using take_nonempty by (rule last_conv_nth)
    also have " = take (Suc i) p ! i"
      using len_take by simp
    finally have "last (take (Suc i) p) = take (Suc i) p ! i" .
    then show ?thesis
      using i by simp
  qed
  moreover have "walk (take (Suc i) p)"
    using walk_take_Suc[OF walk_p i] .
  moreover have "distinct (take (Suc i) p)"
    using distinct_p by simp
  ultimately show ?thesis
    using take_nonempty unfolding simple_walk_betw_def walk_betw_def by auto
qed

lemma dist_le_simple_walk_weight:
  assumes p: "simple_walk_betw a p b"
  shows "dist a b  walk_weight p"
proof -
  have mem: "walk_weight p  simple_walk_weights a b"
    using p unfolding simple_walk_weights_def by blast
  have fin: "finite (simple_walk_weights a b)"
    using finite_simple_walk_weights .
  show ?thesis
    unfolding dist_def using fin mem by (rule Min_le)
qed

lemma shortest_walk_prefix_dist_le:
  assumes p: "shortest_walk a p b"
    and u: "u  set p"
  shows "dist a u  dist a b"
proof -
  have simple_p: "simple_walk_betw a p b"
    using p unfolding shortest_walk_def by blast
  then have walk_p: "walk p"
    unfolding simple_walk_betw_def walk_betw_def by auto
  obtain i where i: "i < length p" "p ! i = u"
    using u by (auto simp: in_set_conv_nth)
  let ?q = "take (Suc i) p"
  have simple_q: "simple_walk_betw a ?q u"
    using simple_walk_prefix[OF simple_p i(1)] i(2) by simp
  have "dist a u  walk_weight ?q"
    using dist_le_simple_walk_weight[OF simple_q] .
  also have "  walk_weight p"
    using walk_weight_take_Suc_le[OF walk_p i(1)] .
  also have " = dist a b"
    using p unfolding shortest_walk_def by simp
  finally show ?thesis .
qed

lemma through_witness_dist_le:
  assumes "through S v"
  obtains u p where "u  S" "shortest_walk s p v" "u  set p" "dist s u  dist s v"
proof -
  obtain u p where u: "u  S" "shortest_walk s p v" "u  set p"
    using assms unfolding through_def by blast
  then have "dist s u  dist s v"
    using shortest_walk_prefix_dist_le by blast
  then show thesis
    using that u by blast
qed

lemma walk_snoc:
  assumes walk_p: "walk p"
    and nonempty: "p  []"
    and last_p: "last p = u"
    and edge: "(u, v)  E"
  shows "walk (p @ [v])"
  using walk_p nonempty last_p edge
proof (induction p arbitrary: u)
  case Nil
  then show ?case by simp
next
  case (Cons x ps)
  show ?case
  proof (cases ps)
    case Nil
    then have xu: "x = u"
      using Cons.prems by simp
    have xv: "(x, v)  E"
      using Cons.prems xu by simp
    have xV: "x  V"
      using Cons.prems Nil by simp
    have vV: "v  V"
      using edge edge_in_V by blast
    show ?thesis
      using xV vV xv Nil by auto
  next
    case (Cons y ys)
    have ps_walk: "walk ps"
      using Cons.prems Cons by auto
    have ps_nonempty: "ps  []"
      using Cons by simp
    have ps_last: "last ps = u"
      using Cons.prems ps_nonempty by simp
    have tail_edge: "(u, v)  E"
      using Cons.prems by simp
    have "walk (ps @ [v])"
      using Cons.IH[OF ps_walk ps_nonempty ps_last tail_edge] .
    then show ?thesis
      using Cons.prems Cons by auto
  qed
qed

lemma walk_weight_snoc:
  assumes "p  []" "last p = u"
  shows "walk_weight (p @ [v]) = walk_weight p + w u v"
  using assms
proof (induction p arbitrary: u)
  case Nil
  then show ?case by simp
next
  case (Cons x ps)
  show ?case
  proof (cases ps)
    case Nil
    then show ?thesis
      using Cons by simp
  next
    case (Cons y ys)
    have ps_ne: "ps  []"
      using Cons by simp
    have ps_last: "last ps = u"
      using Cons.prems ps_ne by simp
    have "walk_weight (ps @ [v]) = walk_weight ps + w u v"
      using Cons.IH[OF ps_ne ps_last] .
    then show ?thesis
      using Cons by simp
  qed
qed

lemma walk_append_tl:
  assumes walk_p: "walk p"
    and walk_q: "walk q"
    and p_ne: "p  []"
    and q_ne: "q  []"
    and join: "last p = hd q"
  shows "walk (p @ tl q)"
  using walk_p walk_q p_ne q_ne join
proof (induction p arbitrary: q)
  case Nil
  then show ?case by simp
next
  case (Cons x ps)
  show ?case
  proof (cases ps)
    case Nil
    have q_ne': "q  []"
      using Cons.prems by blast
    have walk_q': "walk q"
      using Cons.prems by blast
    have "x = hd q"
      using Cons.prems Nil by simp
    then have "x # tl q = q"
      using q_ne' by (cases q) auto
    then show ?thesis
      using walk_q' Nil by simp
  next
    case (Cons y ys)
    have tail_walk: "walk (y # ys)"
      using Cons.prems Cons by auto
    have tail_walk_ps: "walk ps"
      using tail_walk Cons by simp
    have tail_ne: "y # ys  []"
      by simp
    have tail_ne_ps: "ps  []"
      using Cons by simp
    have tail_join: "last (y # ys) = hd q"
      using Cons.prems Cons by simp
    have tail_join_ps: "last ps = hd q"
      using tail_join Cons by simp
    have walk_q': "walk q"
      using Cons.prems by blast
    have q_ne': "q  []"
      using Cons.prems by blast
    have "walk (ps @ tl q)"
      using Cons.IH[OF tail_walk_ps walk_q' tail_ne_ps q_ne' tail_join_ps] .
    then have "walk ((y # ys) @ tl q)"
      using Cons by simp
    then show ?thesis
      using Cons.prems Cons by auto
  qed
qed

lemma walk_weight_append_tl:
  assumes p_ne: "p  []"
    and q_ne: "q  []"
    and join: "last p = hd q"
  shows "walk_weight (p @ tl q) = walk_weight p + walk_weight q"
  using p_ne q_ne join
proof (induction p arbitrary: q)
  case Nil
  then show ?case by simp
next
  case (Cons x ps)
  show ?case
  proof (cases ps)
    case Nil
    have q_ne': "q  []"
      using Cons.prems by blast
    have "x = hd q"
      using Cons.prems Nil by simp
    then have "x # tl q = q"
      using q_ne' by (cases q) auto
    then show ?thesis
      using Nil by simp
  next
    case (Cons y ys)
    have tail_ne: "y # ys  []"
      by simp
    have tail_ne_ps: "ps  []"
      using Cons by simp
    have tail_join: "last (y # ys) = hd q"
      using Cons.prems Cons by simp
    have tail_join_ps: "last ps = hd q"
      using tail_join Cons by simp
    have q_ne': "q  []"
      using Cons.prems by blast
    have "walk_weight (ps @ tl q) = walk_weight ps + walk_weight q"
      using Cons.IH[OF tail_ne_ps q_ne' tail_join_ps] .
    then have "walk_weight ((y # ys) @ tl q) =
        walk_weight (y # ys) + walk_weight q"
      using Cons by simp
    then show ?thesis
      using Cons by simp
  qed
qed

lemma walk_betw_append_tl:
  assumes p: "walk_betw a p b"
    and q: "walk_betw b q c"
  shows "walk_betw a (p @ tl q) c"
proof -
  have p_ne: "p  []" and q_ne: "q  []"
    and hd_p: "hd p = a" and last_p: "last p = b"
    and hd_q: "hd q = b" and last_q: "last q = c"
    and walk_p: "walk p" and walk_q: "walk q"
    using p q unfolding walk_betw_def by auto
  have join: "last p = hd q"
    using last_p hd_q by simp
  have walk: "walk (p @ tl q)"
    using walk_append_tl[OF walk_p walk_q p_ne q_ne join] .
  have nonempty: "p @ tl q  []"
    using p_ne by simp
  have hd: "hd (p @ tl q) = a"
    using hd_p p_ne by simp
  have last: "last (p @ tl q) = c"
  proof (cases "tl q = []")
    case True
    then have "q = [hd q]"
      using q_ne by (cases q) auto
    then show ?thesis
      using last_p hd_q last_q by simp
  next
    case False
    then show ?thesis
      using last_q q_ne by (cases q) auto
  qed
  show ?thesis
    using nonempty hd last walk unfolding walk_betw_def by blast
qed

lemma walk_weight_append_tl_betw:
  assumes p: "walk_betw a p b"
    and q: "walk_betw b q c"
  shows "walk_weight (p @ tl q) = walk_weight p + walk_weight q"
proof -
  have "p  []" "q  []" "last p = hd q"
    using p q unfolding walk_betw_def by auto
  then show ?thesis
    by (rule walk_weight_append_tl)
qed

lemma walk_suffix_append:
  assumes "walk (xs @ ys)" "ys  []"
  shows "walk ys"
  using assms
proof (induction xs)
  case Nil
  then show ?case by simp
next
  case (Cons x xs)
  show ?case
  proof (cases xs)
    case Nil
    then show ?thesis
      using Cons by (cases ys) auto
  next
    case (Cons y zs)
    then have tail_walk: "walk ((y # zs) @ ys)"
      using Cons.prems by auto
    have tail_walk_ps: "walk (xs @ ys)"
      using tail_walk Cons by simp
    then show ?thesis
      using Cons.IH[OF tail_walk_ps Cons.prems(2)] by blast
  qed
qed

lemma walk_prefix_append:
  assumes "walk (xs @ ys)" "xs  []"
  shows "walk xs"
  using assms
proof (induction xs arbitrary: ys)
  case Nil
  then show ?case by simp
next
  case (Cons x xs)
  show ?case
  proof (cases xs)
    case Nil
    then have "x  V"
      using Cons.prems by (cases ys) auto
    then show ?thesis
      using Nil by simp
  next
    case (Cons y zs)
    then have xV: "x  V" and edge: "(x, y)  E" and tail_walk: "walk ((y # zs) @ ys)"
      using Cons.prems by auto
    have tail_walk_ps: "walk (xs @ ys)"
      using tail_walk Cons by simp
    have tail_ne: "xs  []"
      using Cons by simp
    have "walk xs"
      using Cons.IH[OF tail_walk_ps tail_ne] .
    then have "walk (y # zs)"
      using Cons by simp
    then show ?thesis
      using Cons xV edge by auto
  qed
qed

lemma walk_remove_cycle:
  assumes walk_p: "walk (xs @ y # ys @ y # zs)"
  shows "walk (xs @ y # zs)"
proof -
  let ?pref = "xs @ [y]"
  let ?cycle_suffix = "y # ys @ y # zs"
  let ?suffix = "y # zs"
  have pref_walk: "walk ?pref"
    using walk_prefix_append[of ?pref "ys @ y # zs"] walk_p by simp
  have suffix_walk: "walk ?suffix"
    using walk_suffix_append[of "xs @ y # ys" ?suffix] walk_p by simp
  have pref_ne: "?pref  []"
    by simp
  have suffix_ne: "?suffix  []"
    by simp
  have join: "last ?pref = hd ?suffix"
    by simp
  have "walk (?pref @ tl ?suffix)"
    using walk_append_tl[OF pref_walk suffix_walk pref_ne suffix_ne join] .
  then show ?thesis
    by simp
qed

lemma walk_weight_remove_cycle_le:
  assumes walk_p: "walk (xs @ y # ys @ y # zs)"
  shows "walk_weight (xs @ y # zs)  walk_weight (xs @ y # ys @ y # zs)"
proof -
  let ?pref = "xs @ [y]"
  let ?cycle = "y # ys @ [y]"
  let ?suffix = "y # zs"
  let ?cycle_suffix = "y # ys @ y # zs"
  have pref_walk: "walk ?pref"
    using walk_prefix_append[of ?pref "ys @ y # zs"] walk_p by simp
  have cycle_suffix_walk: "walk ?cycle_suffix"
    using walk_suffix_append[of xs ?cycle_suffix] walk_p by simp
  have cycle_walk: "walk ?cycle"
    using walk_prefix_append[of ?cycle zs] cycle_suffix_walk by simp
  have suffix_walk: "walk ?suffix"
    using walk_suffix_append[of "xs @ y # ys" ?suffix] walk_p by simp
  have pref_ne: "?pref  []" and cycle_suffix_ne: "?cycle_suffix  []"
    and cycle_ne: "?cycle  []" and suffix_ne: "?suffix  []"
    by simp_all
  have pref_join_cycle_suffix: "last ?pref = hd ?cycle_suffix"
    by simp
  have pref_join_suffix: "last ?pref = hd ?suffix"
    by simp
  have cycle_join_suffix: "last ?cycle = hd ?suffix"
    by simp
  have new_weight:
    "walk_weight (xs @ y # zs) = walk_weight ?pref + walk_weight ?suffix"
    using walk_weight_append_tl[OF pref_ne suffix_ne pref_join_suffix] by simp
  have original_weight:
    "walk_weight (xs @ y # ys @ y # zs) =
      walk_weight ?pref + walk_weight ?cycle_suffix"
    using walk_weight_append_tl[OF pref_ne cycle_suffix_ne pref_join_cycle_suffix] by simp
  have cycle_suffix_weight:
    "walk_weight ?cycle_suffix = walk_weight ?cycle + walk_weight ?suffix"
    using walk_weight_append_tl[OF cycle_ne suffix_ne cycle_join_suffix] by simp
  have cycle_nonneg: "0  walk_weight ?cycle"
    using walk_weight_nonneg[OF cycle_walk] .
  show ?thesis
    using new_weight original_weight cycle_suffix_weight cycle_nonneg by linarith
qed

lemma walk_betw_remove_cycle:
  assumes p: "walk_betw a (xs @ y # ys @ y # zs) b"
  shows "walk_betw a (xs @ y # zs) b"
proof -
  let ?p = "xs @ y # ys @ y # zs"
  let ?p' = "xs @ y # zs"
  have walk_p: "walk ?p"
    using p unfolding walk_betw_def by blast
  have walk_p': "walk ?p'"
    using walk_remove_cycle[OF walk_p] .
  have nonempty: "?p'  []"
    by simp
  have hd_p: "hd ?p = a" and last_p: "last ?p = b"
    using p unfolding walk_betw_def by auto
  have hd_p': "hd ?p' = a"
    using hd_p by (cases xs) auto
  have last_p': "last ?p' = b"
    using last_p by (cases zs) auto
  show ?thesis
    using nonempty hd_p' last_p' walk_p' unfolding walk_betw_def by blast
qed

lemma walk_betw_to_simple_walk_le_exists:
  assumes p: "walk_betw a p b"
  shows "q. simple_walk_betw a q b  walk_weight q  walk_weight p"
  using p
proof (induction "length p" arbitrary: p rule: less_induct)
  case less
  show ?case
  proof (cases "distinct p")
    case True
    then have "simple_walk_betw a p b"
      using less.prems unfolding simple_walk_betw_def by blast
    then show ?thesis
      by blast
  next
    case False
    then obtain xs ys zs y where decomp: "p = xs @ [y] @ ys @ [y] @ zs"
      using not_distinct_decomp by blast
    let ?p' = "xs @ y # zs"
    have p_form: "p = xs @ y # ys @ y # zs"
      using decomp by simp
    have p'_betw: "walk_betw a ?p' b"
      using walk_betw_remove_cycle[of a xs y ys zs b] less.prems p_form by simp
    have p'_shorter: "length ?p' < length p"
      using p_form by simp
    obtain q where q: "simple_walk_betw a q b" "walk_weight q  walk_weight ?p'"
      using less.hyps[OF p'_shorter p'_betw] by blast
    have "walk_weight ?p'  walk_weight p"
      using walk_weight_remove_cycle_le[of xs y ys zs] less.prems p_form
      unfolding walk_betw_def by simp
    then have "walk_weight q  walk_weight p"
      using q(2) by linarith
    then show ?thesis
      using q(1) by blast
  qed
qed

lemma walk_betw_to_simple_walk_le:
  assumes p: "walk_betw a p b"
  obtains q where "simple_walk_betw a q b" "walk_weight q  walk_weight p"
  using walk_betw_to_simple_walk_le_exists[OF p] that by blast

lemma walk_suffix_from_index:
  assumes p: "simple_walk_betw a p b"
    and i: "i < length p"
  shows "walk_betw (p ! i) (drop i p) b"
proof -
  have walk_p: "walk p" and last_p: "last p = b"
    using p unfolding simple_walk_betw_def walk_betw_def by auto
  have drop_ne: "drop i p  []"
    using i by auto
  have hd_drop: "hd (drop i p) = p ! i"
    using i by (simp add: hd_drop_conv_nth)
  have last_drop: "last (drop i p) = b"
    using last_p i by (simp add: last_drop)
  have p_decomp: "p = take i p @ drop i p"
    by simp
  have walk_drop: "walk (drop i p)"
    using walk_suffix_append[of "take i p" "drop i p"] walk_p drop_ne p_decomp by simp
  show ?thesis
    using drop_ne hd_drop last_drop walk_drop unfolding walk_betw_def by blast
qed

lemma take_Suc_append_tl_drop:
  assumes "i < length p"
  shows "take (Suc i) p @ tl (drop i p) = p"
proof -
  have drop_i: "drop i p = p ! i # drop (Suc i) p"
    using Cons_nth_drop_Suc[OF assms] by simp
  have "take (Suc i) p @ tl (drop i p) =
      take i p @ [p ! i] @ drop (Suc i) p"
    using assms drop_i by (simp add: take_Suc_conv_app_nth)
  also have " = take i p @ drop i p"
    using drop_i by simp
  finally show ?thesis
    by simp
qed

lemma shortest_walk_prefix_shortest:
  assumes sp: "shortest_walk a p b"
    and i: "i < length p"
  shows "shortest_walk a (take (Suc i) p) (p ! i)"
proof -
  let ?u = "p ! i"
  let ?pref = "take (Suc i) p"
  let ?suf = "drop i p"
  have simple_p: "simple_walk_betw a p b"
    using sp unfolding shortest_walk_def by blast
  have weight_p: "walk_weight p = dist a b"
    using sp unfolding shortest_walk_def by blast
  have pref_simple: "simple_walk_betw a ?pref ?u"
    using simple_walk_prefix[OF simple_p i] .
  have pref_walk_betw: "walk_betw a ?pref ?u"
    using pref_simple unfolding simple_walk_betw_def by blast
  have suf_walk_betw: "walk_betw ?u ?suf b"
    using walk_suffix_from_index[OF simple_p i] .
  have split_p: "?pref @ tl ?suf = p"
    using take_Suc_append_tl_drop[OF i] .
  have weight_split: "walk_weight p = walk_weight ?pref + walk_weight ?suf"
    using walk_weight_append_tl_betw[OF pref_walk_betw suf_walk_betw] split_p by simp
  have dist_pref_le: "dist a ?u  walk_weight ?pref"
    using dist_le_simple_walk_weight[OF pref_simple] .
  have no_strict: "¬ dist a ?u < walk_weight ?pref"
  proof
    assume strict: "dist a ?u < walk_weight ?pref"
    have reach_u: "reachable a ?u"
      using pref_simple unfolding reachable_def by blast
    obtain r where r_short: "shortest_walk a r ?u"
      using shortest_walk_exists[OF reach_u] by blast
    have r_simple: "simple_walk_betw a r ?u"
      using r_short unfolding shortest_walk_def by blast
    have r_walk_betw: "walk_betw a r ?u"
      using r_simple unfolding simple_walk_betw_def by blast
    have r_weight: "walk_weight r = dist a ?u"
      using r_short unfolding shortest_walk_def by blast
    have concat_walk: "walk_betw a (r @ tl ?suf) b"
      using walk_betw_append_tl[OF r_walk_betw suf_walk_betw] .
    have concat_weight: "walk_weight (r @ tl ?suf) = walk_weight r + walk_weight ?suf"
      using walk_weight_append_tl_betw[OF r_walk_betw suf_walk_betw] .
    obtain q where q: "simple_walk_betw a q b"
      "walk_weight q  walk_weight (r @ tl ?suf)"
      using walk_betw_to_simple_walk_le[OF concat_walk] by blast
    have "walk_weight (r @ tl ?suf) < walk_weight p"
      using strict r_weight weight_split concat_weight by linarith
    then have "walk_weight q < dist a b"
      using q(2) weight_p by linarith
    moreover have "dist a b  walk_weight q"
      using dist_le_simple_walk_weight[OF q(1)] .
    ultimately show False
      by linarith
  qed
  have "walk_weight ?pref = dist a ?u"
    using dist_pref_le no_strict by linarith
  then show ?thesis
    using pref_simple unfolding shortest_walk_def by blast
qed

lemma walk_nth_edge:
  assumes "walk p"
    and "Suc i < length p"
  shows "(p ! i, p ! Suc i)  E"
  using assms
proof (induction p arbitrary: i)
  case Nil
  then show ?case by simp
next
  case (Cons x xs)
  show ?case
  proof (cases i)
    case 0
    then show ?thesis
      using Cons.prems by (cases xs) auto
  next
    case (Suc j)
    then have "Suc j < length xs"
      using Cons.prems by simp
    moreover have "walk xs"
      using Cons.prems Suc by (cases xs) auto
    ultimately have "(xs ! j, xs ! Suc j)  E"
      using Cons.IH by blast
    then show ?thesis
      using Suc by simp
  qed
qed

lemma simple_walk_snoc:
  assumes p: "simple_walk_betw s p u"
    and edge: "(u, v)  E"
    and fresh: "v  set p"
  shows "simple_walk_betw s (p @ [v]) v"
proof -
  have nonempty: "p  []" and hd: "hd p = s" and last_p: "last p = u"
    and walk_p: "walk p" and distinct_p: "distinct p"
    using p unfolding simple_walk_betw_def walk_betw_def by auto
  have "walk (p @ [v])"
    using walk_snoc[OF walk_p nonempty last_p edge] .
  moreover have "p @ [v]  []" "hd (p @ [v]) = s" "last (p @ [v]) = v" "distinct (p @ [v])"
    using nonempty hd fresh distinct_p by auto
  ultimately show ?thesis
    unfolding simple_walk_betw_def walk_betw_def by auto
qed

lemma dist_triangle_edge:
  assumes edge: "(u, v)  E"
    and reach_u: "reachable s u"
  shows "reachable s v" "dist s v  dist s u + w u v"
proof -
  obtain p where p: "shortest_walk s p u"
    using reach_u by (rule shortest_walk_exists)
  have simple_p: "simple_walk_betw s p u"
    using p unfolding shortest_walk_def by blast
  have weight_p: "walk_weight p = dist s u"
    using p unfolding shortest_walk_def by blast
  show reach_v: "reachable s v"
  proof (cases "v  set p")
    case True
    then obtain i where i: "i < length p" "p ! i = v"
      by (auto simp: in_set_conv_nth)
    have "simple_walk_betw s (take (Suc i) p) v"
      using simple_walk_prefix[OF simple_p i(1)] i(2) by simp
    then show ?thesis
      unfolding reachable_def by blast
  next
    case False
    have "simple_walk_betw s (p @ [v]) v"
      using simple_walk_snoc[OF simple_p edge False] .
    then show ?thesis
      unfolding reachable_def by blast
  qed
  show "dist s v  dist s u + w u v"
  proof (cases "v  set p")
    case True
    have "dist s v  dist s u"
      using shortest_walk_prefix_dist_le[OF p True] .
    moreover have "0  w u v"
      using edge nonneg_weight by auto
    ultimately show ?thesis
      by linarith
  next
    case False
    have simple_q: "simple_walk_betw s (p @ [v]) v"
      using simple_walk_snoc[OF simple_p edge False] .
    have "dist s v  walk_weight (p @ [v])"
      using dist_le_simple_walk_weight[OF simple_q] .
    also have " = dist s u + w u v"
      using simple_p weight_p unfolding simple_walk_betw_def walk_betw_def
      by (simp add: walk_weight_snoc)
    finally show ?thesis .
  qed
qed

definition sound_label where
  "sound_label d  (vV. reachable s v  dist s v  d v)"

definition relax_edge where
  "relax_edge d u v = (λx. if x = v then min (d v) (d u + w u v) else d x)"

definition tight_edge_step where
  "tight_edge_step u v 
     (u, v)  E  reachable s u  dist s v = dist s u + w u v"

lemma relax_edge_le:
  "relax_edge d u v x  d x"
  unfolding relax_edge_def by simp

lemma shortest_walk_successively_tight:
  assumes sp: "shortest_walk s p v"
  shows "successively tight_edge_step p"
  unfolding successively_conv_nth
proof clarify
  fix i
  assume i: "Suc i < length p"
  have simple_p: "simple_walk_betw s p v"
    using sp unfolding shortest_walk_def by blast
  have walk_p: "walk p"
    using simple_p unfolding simple_walk_betw_def walk_betw_def by blast
  have edge: "(p ! i, p ! Suc i)  E"
    using walk_nth_edge[OF walk_p i] .
  have pref_i: "shortest_walk s (take (Suc i) p) (p ! i)"
    using shortest_walk_prefix_shortest[OF sp] i by simp
  have pref_Suc_i: "shortest_walk s (take (Suc (Suc i)) p) (p ! Suc i)"
    using shortest_walk_prefix_shortest[OF sp i] .
  have reach_i: "reachable s (p ! i)"
    using pref_i unfolding shortest_walk_def reachable_def by blast
  have pref_i_ne: "take (Suc i) p  []"
    using i by (cases p) auto
  have last_pref_i: "last (take (Suc i) p) = p ! i"
  proof -
    have len_take: "length (take (Suc i) p) = Suc i"
      using i by simp
    have "last (take (Suc i) p) = take (Suc i) p ! i"
      using last_conv_nth[OF pref_i_ne] len_take by simp
    then show ?thesis
      using i by simp
  qed
  have take_next:
    "take (Suc (Suc i)) p = take (Suc i) p @ [p ! Suc i]"
    using i by (simp add: take_Suc_conv_app_nth)
  have weight_step:
    "walk_weight (take (Suc (Suc i)) p) =
      walk_weight (take (Suc i) p) + w (p ! i) (p ! Suc i)"
    using walk_weight_snoc[OF pref_i_ne last_pref_i, of "p ! Suc i"] take_next by simp
  have dist_i: "walk_weight (take (Suc i) p) = dist s (p ! i)"
    using pref_i unfolding shortest_walk_def by blast
  have dist_Suc_i: "walk_weight (take (Suc (Suc i)) p) = dist s (p ! Suc i)"
    using pref_Suc_i unfolding shortest_walk_def by blast
  show "tight_edge_step (p ! i) (p ! Suc i)"
    using edge reach_i weight_step dist_i dist_Suc_i
    unfolding tight_edge_step_def by simp
qed

lemma relax_edge_sound:
  assumes sound: "sound_label d"
    and edge: "(u, v)  E"
    and reach_u: "reachable s u"
  shows "sound_label (relax_edge d u v)"
proof -
  have uV: "u  V"
    using edge edge_in_V by blast
  have dist_u_le: "dist s u  d u"
    using sound uV reach_u unfolding sound_label_def by blast
  have triangle: "dist s v  dist s u + w u v"
    using dist_triangle_edge[OF edge reach_u] by blast
  show ?thesis
    unfolding sound_label_def relax_edge_def
  proof clarify
    fix x
    assume xV: "x  V" and reach_x: "reachable s x"
    show "dist s x  (if x = v then min (d v) (d u + w u v) else d x)"
    proof (cases "x = v")
      case True
      have "dist s v  d v"
        using sound xV reach_x True unfolding sound_label_def by blast
      moreover have "dist s v  d u + w u v"
        using dist_u_le triangle by linarith
      ultimately show ?thesis
        using True by simp
    next
      case False
      then show ?thesis
      using sound xV reach_x unfolding sound_label_def by simp
    qed
  qed
qed

lemma relax_tight_edge_completes:
  assumes sound: "sound_label d"
    and complete_u: "d u = dist s u"
    and tight: "tight_edge_step u v"
  shows "sound_label (relax_edge d u v)"
    and "relax_edge d u v v = dist s v"
proof -
  have edge: "(u, v)  E" and reach_u: "reachable s u"
    and dist_v: "dist s v = dist s u + w u v"
    using tight unfolding tight_edge_step_def by auto
  show "sound_label (relax_edge d u v)"
    using relax_edge_sound[OF sound edge reach_u] .
  have vV: "v  V"
    using edge edge_in_V by blast
  have reach_v: "reachable s v"
    using dist_triangle_edge[OF edge reach_u] by blast
  have "dist s v  d v"
    using sound vV reach_v unfolding sound_label_def by blast
  then show "relax_edge d u v v = dist s v"
    using complete_u dist_v unfolding relax_edge_def by simp
qed

fun relax_path where
  "relax_path d [] = d"
| "relax_path d [x] = d"
| "relax_path d (x # y # xs) = relax_path (relax_edge d x y) (y # xs)"

fun relax_edges where
  "relax_edges d [] = d"
| "relax_edges d ((u, v) # es) = relax_edges (relax_edge d u v) es"

fun path_edges where
  "path_edges [] = []"
| "path_edges [x] = []"
| "path_edges (x # y # xs) = (x, y) # path_edges (y # xs)"

lemma relax_edges_append:
  "relax_edges d (xs @ ys) = relax_edges (relax_edges d xs) ys"
  by (induction xs arbitrary: d) (auto split: prod.splits)

lemma relax_edges_le:
  "relax_edges d es x  d x"
proof (induction es arbitrary: d)
  case Nil
  then show ?case by simp
next
  case (Cons e es)
  obtain u v where e: "e = (u, v)"
    by force
  have "relax_edges (relax_edge d u v) es x  relax_edge d u v x"
    by (rule Cons.IH)
  also have "  d x"
    by (rule relax_edge_le)
  finally show ?case
    unfolding e by simp
qed

lemma relax_path_eq_relax_edges:
  "relax_path d p = relax_edges d (path_edges p)"
  by (induction p arbitrary: d rule: relax_path.induct) simp_all

lemma relax_edge_preserves_other:
  assumes "x  v"
  shows "relax_edge d u v x = d x"
  using assms unfolding relax_edge_def by simp

lemma relax_edge_preserves_complete_other:
  assumes "x  v" "d x = dist s x"
  shows "relax_edge d u v x = dist s x"
  using assms by (simp add: relax_edge_preserves_other)

lemma relax_edge_preserves_complete_sound:
  assumes sound: "sound_label d"
    and complete_x: "d x = dist s x"
    and edge: "(u, v)  E"
    and reach_u: "reachable s u"
  shows "relax_edge d u v x = dist s x"
proof (cases "x = v")
  case False
  then show ?thesis
    using complete_x by (simp add: relax_edge_preserves_other)
next
  case True
  have vV: "v  V"
    using edge edge_in_V by blast
  have reach_v: "reachable s v"
    using dist_triangle_edge[OF edge reach_u] by blast
  have dist_v_le_dv: "dist s v  d v"
    using sound vV reach_v unfolding sound_label_def by blast
  have dist_v_le_candidate: "dist s v  d u + w u v"
  proof -
    have uV: "u  V"
      using edge edge_in_V by blast
    have "dist s u  d u"
      using sound uV reach_u unfolding sound_label_def by blast
    moreover have "dist s v  dist s u + w u v"
      using dist_triangle_edge[OF edge reach_u] by blast
    ultimately show ?thesis
      by linarith
  qed
  show ?thesis
    using True complete_x dist_v_le_dv dist_v_le_candidate unfolding relax_edge_def by simp
qed

lemma relax_edges_sound:
  assumes sound: "sound_label d"
    and edges: "u v. (u, v)  set es  (u, v)  E"
    and reaches: "u v. (u, v)  set es  reachable s u"
  shows "sound_label (relax_edges d es)"
  using sound edges reaches
proof (induction es arbitrary: d)
  case Nil
  then show ?case by simp
next
  case (Cons e es)
  obtain u v where e: "e = (u, v)"
    by force
  have edge: "(u, v)  E"
    using Cons.prems e by auto
  have reach: "reachable s u"
    using Cons.prems e by auto
  have sound1: "sound_label (relax_edge d u v)"
    using relax_edge_sound[OF Cons.prems(1) edge reach] .
  have edges_tail: "a b. (a, b)  set es  (a, b)  E"
    using Cons.prems by auto
  have reaches_tail: "a b. (a, b)  set es  reachable s a"
    using Cons.prems by auto
  show ?case
    using Cons.IH[OF sound1 edges_tail reaches_tail] e by simp
qed

lemma relax_edges_preserves_complete_if_not_targeted:
  assumes complete_x: "d x = dist s x"
    and not_target: "u. (u, x)  set es"
  shows "relax_edges d es x = dist s x"
  using complete_x not_target
proof (induction es arbitrary: d)
  case Nil
  then show ?case by simp
next
  case (Cons e es)
  obtain u v where e: "e = (u, v)"
    by force
  have x_ne_v: "x  v"
    using Cons.prems(2)[of u] e by auto
  have complete_after: "relax_edge d u v x = dist s x"
    using x_ne_v Cons.prems(1) unfolding relax_edge_def by simp
  have not_target_tail: "a. (a, x)  set es"
    using Cons.prems e by auto
  have "relax_edges (relax_edge d u v) es x = dist s x"
    using Cons.IH[of "relax_edge d u v"] complete_after not_target_tail by blast
  show ?case
    using relax_edges (relax_edge d u v) es x = dist s x e by simp
qed

lemma relax_edges_preserves_complete_sound:
  assumes sound: "sound_label d"
    and complete_x: "d x = dist s x"
    and edges: "u v. (u, v)  set es  (u, v)  E"
    and reaches: "u v. (u, v)  set es  reachable s u"
  shows "relax_edges d es x = dist s x"
  using sound complete_x edges reaches
proof (induction es arbitrary: d)
  case Nil
  then show ?case by simp
next
  case (Cons e es)
  obtain u v where e: "e = (u, v)"
    by force
  have edge: "(u, v)  E"
    using Cons.prems e by auto
  have reach: "reachable s u"
    using Cons.prems e by auto
  have sound1: "sound_label (relax_edge d u v)"
    using relax_edge_sound[OF Cons.prems(1) edge reach] .
  have complete_after: "relax_edge d u v x = dist s x"
    using relax_edge_preserves_complete_sound[OF Cons.prems(1) Cons.prems(2) edge reach] .
  have edges_tail: "a b. (a, b)  set es  (a, b)  E"
    using Cons.prems by auto
  have reaches_tail: "a b. (a, b)  set es  reachable s a"
    using Cons.prems by auto
  have "relax_edges (relax_edge d u v) es x = dist s x"
    using Cons.IH[OF sound1 complete_after edges_tail reaches_tail] .
  then show ?case
    using e by simp
qed

lemma relax_path_tight_sound_complete:
  assumes nonempty: "p  []"
    and sound: "sound_label d"
    and complete_hd: "d (hd p) = dist s (hd p)"
    and tight: "successively tight_edge_step p"
  shows "sound_label (relax_path d p)"
    and "relax_path d p (last p) = dist s (last p)"
proof -
  have combined: "d. p  []  sound_label d 
      d (hd p) = dist s (hd p)  successively tight_edge_step p 
      sound_label (relax_path d p) 
      relax_path d p (last p) = dist s (last p)"
  proof (induction p)
    case Nil
    then show ?case by simp
  next
    case (Cons x ps)
    show ?case
    proof (cases "ps = []")
      case True
      then show ?thesis
        using Cons.prems by simp
    next
      case False
      then obtain y ys where ps: "ps = y # ys"
        by (cases ps) auto
      have step: "tight_edge_step x y"
        using Cons.prems ps by (simp add: successively_Cons)
      have tight_tail: "successively tight_edge_step (y # ys)"
        using Cons.prems ps by (simp add: successively_Cons)
      let ?d1 = "relax_edge d x y"
      have complete_x: "d x = dist s x"
        using Cons.prems by simp
      have sound1: "sound_label ?d1"
        using relax_tight_edge_completes(1)[OF Cons.prems(2) complete_x step] .
      have complete_y: "?d1 y = dist s y"
        using relax_tight_edge_completes(2)[OF Cons.prems(2) complete_x step] .
      have complete_hd_ps: "?d1 (hd ps) = dist s (hd ps)"
        using complete_y ps by simp
      have tight_ps: "successively tight_edge_step ps"
        using tight_tail ps by simp
      have tail: "sound_label (relax_path ?d1 ps) 
          relax_path ?d1 ps (last ps) = dist s (last ps)"
        using Cons.IH[of ?d1] False sound1 complete_hd_ps tight_ps by blast
      then show ?thesis
        using ps by simp
    qed
  qed
  then show "sound_label (relax_path d p)"
    using assms by blast
  from combined show "relax_path d p (last p) = dist s (last p)"
    using assms by blast
qed

lemma relax_edges_with_tight_path_prefix_complete:
  assumes nonempty: "p  []"
    and sound: "sound_label d"
    and complete_hd: "d (hd p) = dist s (hd p)"
    and tight: "successively tight_edge_step p"
    and es: "es = path_edges p @ extra"
    and extra_edges: "u v. (u, v)  set extra  (u, v)  E"
    and extra_reaches: "u v. (u, v)  set extra  reachable s u"
  shows "sound_label (relax_edges d es)"
    and "relax_edges d es (last p) = dist s (last p)"
proof -
  let ?d1 = "relax_edges d (path_edges p)"
  have path_eq: "?d1 = relax_path d p"
    using relax_path_eq_relax_edges[of d p] by simp
  have sound1: "sound_label ?d1"
    using relax_path_tight_sound_complete(1)[OF nonempty sound complete_hd tight] path_eq by simp
  have complete_last: "?d1 (last p) = dist s (last p)"
    using relax_path_tight_sound_complete(2)[OF nonempty sound complete_hd tight] path_eq by simp
  have full: "relax_edges d es = relax_edges ?d1 extra"
    using es relax_edges_append[of d "path_edges p" extra] by simp
  show "sound_label (relax_edges d es)"
    using relax_edges_sound[OF sound1 extra_edges extra_reaches] full by simp
  show "relax_edges d es (last p) = dist s (last p)"
    using relax_edges_preserves_complete_sound[OF sound1 complete_last extra_edges extra_reaches] full by simp
qed

end

end