Theory Common

section "Common"

theory Common
  imports
  "HOL-Library.Going_To_Filter"
  "Akra_Bazzi.Akra_Bazzi_Method"
  "Akra_Bazzi.Akra_Bazzi_Approximation"
  "HOL-Library.Code_Target_Numeral"
  "Root_Balanced_Tree.Time_Monad"
begin

type_synonym point = "int * int"

subsection "Auxiliary Functions and Lemmas"

subsubsection "Time Monad"

lemma time_distrib_bind:
  "time (bind_tm tm f) = time tm + time (f (val tm))"
  unfolding bind_tm_def by (simp split: tm.split)

lemmas time_simps = time_distrib_bind tick_def

lemma bind_tm_cong[fundef_cong]:
  assumes "v. v = val n  f v = g v" "m = n"
  shows "bind_tm m f = bind_tm n g"
  using assms unfolding bind_tm_def by (auto split: tm.split)

subsubsection "Landau Auxiliary"

text ‹
  The following lemma expresses a procedure for deriving complexity properties of
  the form @{prop"t  O[m going_to at_top within A](f o m)"} where
     t› is a (timing) function on same data domain (e.g. lists),
     m› is a measure function on that data domain (e.g. length),
     t'› is a function on @{typ nat},
     A› is the set of valid inputs for the data domain.
  One needs to show that
     t› is bounded by @{term "t' o m"} for valid inputs
     @{prop"t'  O(f)"}
  to conclude the overall property @{prop"t  O[m going_to at_top within A](f o m)"}.
›

lemma bigo_measure_trans:
  fixes t :: "'a  real" and t' :: "nat  real" and m :: "'a  nat" and f ::"nat  real"
  assumes "x. x  A  t x  (t' o m) x"
      and "t'  O(f)"
      and "x. x  A  0  t x"
  shows "t  O[m going_to at_top within A](f o m)"
proof -
  have 0: "x. x  A  0  (t' o m) x" by (meson assms(1,3) order_trans)
  have 1: "t  O[m going_to at_top within A](t' o m)"
    apply(rule bigoI[where c=1]) using assms 0
    by (simp add: eventually_inf_principal going_to_within_def)
  have 2: "t' o m  O[m going_to at_top](f o m)"
    unfolding o_def going_to_def
    by(rule landau_o.big.filtercomap[OF assms(2)])
  have 3: "t' o m  O[m going_to at_top within A](f o m)"
    using landau_o.big.filter_mono[OF _2] going_to_mono[OF _subset_UNIV] by blast
  show ?thesis by(rule landau_o.big_trans[OF 1 3])
qed

lemma const_1_bigo_n_ln_n:
  "(λ(n::nat). 1)  O(λn. n * ln n)"
proof -
  have "N. (n::nat)  N. (λx. 1  x * ln x) n"
  proof -
    have "(n::nat)  3. (λx. 1  x * ln x) n"
    proof standard
      fix n
      show "3  n  1  real n * ln (real n)"
      proof standard
        assume "3  n"
        hence "1  real n"
          by simp
        moreover have "1  ln (real n)" 
          using ln_ln_nonneg' 3  n by simp
        ultimately show "1  real n * ln (real n)"
          by (auto simp: order_trans)
      qed
    qed
    thus ?thesis
      by blast
  qed
  thus ?thesis
    by auto
qed

subsubsection "Miscellaneous Lemmas"

lemma set_take_drop_i_le_j:
  "i  j  set xs = set (take j xs)  set (drop i xs)"
proof (induction xs arbitrary: i j)
  case (Cons x xs)
  show ?case
  proof (cases "i = 0")
    case True
    thus ?thesis
      using set_take_subset by force
  next
    case False
    hence "set xs = set (take (j - 1) xs)  set (drop (i - 1) xs)"
      by (simp add: Cons diff_le_mono)
    moreover have "set (take j (x # xs)) = insert x (set (take (j - 1) xs))"
      using False Cons.prems by (auto simp: take_Cons')
    moreover have "set (drop i (x # xs)) = set (drop (i - 1) xs)"
      using False Cons.prems by (auto simp: drop_Cons')
    ultimately show ?thesis
      by auto
  qed
qed simp

lemma set_take_drop:
  "set xs = set (take n xs)  set (drop n xs)"
  using set_take_drop_i_le_j by fast

lemma sorted_wrt_take_drop:
  "sorted_wrt f xs  x  set (take n xs). y  set (drop n xs). f x y"
  using sorted_wrt_append[of f "take n xs" "drop n xs"] by simp

lemma sorted_wrt_hd_less:
  assumes "sorted_wrt f xs" "x. f x x"
  shows "x  set xs. f (hd xs) x"
  using assms by (cases xs) auto

lemma sorted_wrt_hd_less_take:
  assumes "sorted_wrt f (x # xs)" "x. f x x"
  shows "y  set (take n (x # xs)). f x y"
  using assms sorted_wrt_hd_less [of f x # xs] in_set_takeD [of _ n x # xs]
  by auto

lemma sorted_wrt_take_less_hd_drop:
  assumes "sorted_wrt f xs" "n < length xs"
  shows "x  set (take n xs). f x (hd (drop n xs))"
  using sorted_wrt_take_drop assms by fastforce

lemma sorted_wrt_hd_drop_less_drop:
  assumes "sorted_wrt f xs" "x. f x x"
  shows "x  set (drop n xs). f (hd (drop n xs)) x"
  using assms sorted_wrt_drop sorted_wrt_hd_less by blast

lemma length_filter_P_impl_Q:
   "(x. P x  Q x)  length (filter P xs)  length (filter Q xs)"
  by (induction xs) auto

lemma filter_Un:
  "set xs = A  B  set (filter P xs) = { x  A. P x }  { x  B. P x }"
  by (induction xs) (auto, metis UnI1 insert_iff, metis UnI2 insert_iff)

subsubsection @{const length}

fun length_tm :: "'a list  nat tm" where
  "length_tm [] =1 return 0"
| "length_tm (x # xs) =1
    do {
      l <- length_tm xs;
      return (1 + l)
    }"

lemma length_eq_val_length_tm:
  "val (length_tm xs) = length xs"
  by (induction xs) auto

lemma time_length_tm:
  "time (length_tm xs) = length xs + 1"
  by (induction xs) (auto simp: time_simps)

fun length_it' :: "nat  'a list  nat" where
  "length_it' acc [] = acc"
| "length_it' acc (x#xs) = length_it' (acc+1) xs"

definition length_it :: "'a list  nat" where
  "length_it xs = length_it' 0 xs"

lemma length_conv_length_it':
  "length xs + acc = length_it' acc xs"
  by (induction acc xs rule: length_it'.induct) auto

lemma length_conv_length_it[code_unfold]:
  "length xs = length_it xs"
  unfolding length_it_def using length_conv_length_it' add_0_right by metis

subsubsection @{const rev}

fun rev_it' :: "'a list  'a list  'a list" where
  "rev_it' acc [] = acc"
| "rev_it' acc (x#xs) = rev_it' (x#acc) xs"

definition rev_it :: "'a list  'a list" where
  "rev_it xs = rev_it' [] xs"

lemma rev_conv_rev_it':
  "rev xs @ acc = rev_it' acc xs"
  by (induction acc xs rule: rev_it'.induct) auto

lemma rev_conv_rev_it[code_unfold]:
  "rev xs = rev_it xs"
  unfolding rev_it_def using rev_conv_rev_it' append_Nil2 by metis

subsubsection @{const take}

fun take_tm :: "nat  'a list  'a list tm" where
  "take_tm n [] =1 return []"
| "take_tm n (x # xs) =1
    (case n of
       0  return []
     | Suc m  do {
         ys <- take_tm m xs;
         return (x # ys)
       }
    )"

lemma take_eq_val_take_tm:
  "val (take_tm n xs) = take n xs"
  by (induction xs arbitrary: n) (auto split: nat.split)

lemma time_take_tm:
  "time (take_tm n xs) = min n (length xs) + 1"
  by (induction xs arbitrary: n) (auto simp: time_simps split: nat.split)

subsubsection @{const filter}

fun filter_tm :: "('a  bool)  'a list  'a list tm" where
  "filter_tm P [] =1 return []"
| "filter_tm P (x # xs) =1
    (if P x then
       do {
         ys <- filter_tm P xs;
         return (x # ys)
       }
     else
       filter_tm P xs
    )"

lemma filter_eq_val_filter_tm:
  "val (filter_tm P xs) = filter P xs"
  by (induction xs) auto

lemma time_filter_tm:
  "time (filter_tm P xs) = length xs + 1"
  by (induction xs) (auto simp: time_simps)

fun filter_it' :: "'a list  ('a  bool)  'a list  'a list" where
  "filter_it' acc P [] = rev acc"
| "filter_it' acc P (x#xs) = (
    if P x then
      filter_it' (x#acc) P xs
    else
      filter_it' acc P xs
  )"

definition filter_it :: "('a  bool)  'a list  'a list" where
  "filter_it P xs = filter_it' [] P xs"

lemma filter_conv_filter_it':
  "rev acc @ filter P xs = filter_it' acc P xs"
  by (induction acc P xs rule: filter_it'.induct) auto

lemma filter_conv_filter_it[code_unfold]:
  "filter P xs = filter_it P xs"
  unfolding filter_it_def using filter_conv_filter_it' append_Nil rev.simps(1) by metis

subsubsection split_at›

fun split_at_tm :: "nat  'a list  ('a list × 'a list) tm" where
  "split_at_tm n [] =1 return ([], [])"
| "split_at_tm n (x # xs) =1 (
    case n of
      0  return ([], x # xs)
    | Suc m 
      do {
        (xs', ys') <- split_at_tm m xs;
        return (x # xs', ys')
      }
  )"

fun split_at :: "nat  'a list  'a list × 'a list" where
  "split_at n [] = ([], [])"
| "split_at n (x # xs) = (
    case n of 
      0  ([], x # xs)
    | Suc m  
        let (xs', ys') = split_at m xs in
        (x # xs', ys')
  )"

lemma split_at_eq_val_split_at_tm:
  "val (split_at_tm n xs) = split_at n xs"
  by (induction xs arbitrary: n) (auto split: nat.split prod.split)

lemma split_at_take_drop_conv:
  "split_at n xs = (take n xs, drop n xs)"
  by (induction xs arbitrary: n) (auto simp: split: nat.split)

lemma time_split_at_tm:
  "time (split_at_tm n xs) = min n (length xs) + 1"
  by (induction xs arbitrary: n) (auto simp: time_simps split: nat.split prod.split)

fun split_at_it' :: "'a list  nat  'a list  ('a list * 'a list)" where
  "split_at_it' acc n [] = (rev acc, [])"
| "split_at_it' acc n (x#xs) = (
    case n of
      0  (rev acc, x#xs)
    | Suc m  split_at_it' (x#acc) m xs
  )"

definition split_at_it :: "nat  'a list  ('a list * 'a list)" where
  "split_at_it n xs = split_at_it' [] n xs"

lemma split_at_conv_split_at_it':
  assumes "(ts, ds) = split_at n xs" "(ts', ds') = split_at_it' acc n xs"
  shows "rev acc @ ts = ts'"
    and "ds = ds'"
  using assms
  by (induction acc n xs arbitrary: ts rule: split_at_it'.induct)
     (auto simp: split: prod.splits nat.splits)

lemma split_at_conv_split_at_it_prod:
  assumes "(ts, ds) = split_at n xs" "(ts', ds') = split_at_it n xs"
  shows "(ts, ds) = (ts', ds')"
  using assms unfolding split_at_it_def 
  using split_at_conv_split_at_it' rev.simps(1) append_Nil by fast+

lemma split_at_conv_split_at_it[code_unfold]:
  "split_at n xs = split_at_it n xs"
  using split_at_conv_split_at_it_prod surj_pair by metis

declare split_at_tm.simps [simp del]
declare split_at.simps [simp del]


subsection "Mergesort"

subsubsection "Functional Correctness Proof"

definition sorted_fst :: "point list  bool" where
  "sorted_fst ps = sorted_wrt (λp0 p1. fst p0  fst p1) ps"

definition sorted_snd :: "point list  bool" where
  "sorted_snd ps = sorted_wrt (λp0 p1. snd p0  snd p1) ps"

fun merge_tm :: "('b  'a::linorder)  'b list  'b list  'b list tm" where
  "merge_tm f (x # xs) (y # ys) =1 (
    if f x  f y then
      do {
        tl <- merge_tm f xs (y # ys);
        return (x # tl)
      }
    else
      do {
        tl <- merge_tm f (x # xs) ys;
        return (y # tl)
      }
  )"
| "merge_tm f [] ys =1 return ys"
| "merge_tm f xs [] =1 return xs"

fun merge :: "('b  'a::linorder)  'b list  'b list  'b list" where
  "merge f (x # xs) (y # ys) = (
    if f x  f y then
      x # merge f xs (y # ys)
    else
      y # merge f (x # xs) ys
  )"
| "merge f [] ys = ys"
| "merge f xs [] = xs"

lemma merge_eq_val_merge_tm:
  "val (merge_tm f xs ys) = merge f xs ys"
  by (induction f xs ys rule: merge.induct) auto

lemma length_merge:
  "length (merge f xs ys) = length xs + length ys"
  by (induction f xs ys rule: merge.induct) auto

lemma set_merge:
  "set (merge f xs ys) = set xs  set ys"
  by (induction f xs ys rule: merge.induct) auto

lemma distinct_merge:
  assumes "set xs  set ys = {}" "distinct xs" "distinct ys"
  shows "distinct (merge f xs ys)"
  using assms by (induction f xs ys rule: merge.induct) (auto simp: set_merge)

lemma sorted_merge:
  assumes "P = (λx y. f x  f y)"
  shows "sorted_wrt P (merge f xs ys)  sorted_wrt P xs  sorted_wrt P ys"
  using assms by (induction f xs ys rule: merge.induct) (auto simp: set_merge)

declare split_at_take_drop_conv [simp]

function (sequential) mergesort_tm :: "('b  'a::linorder)  'b list  'b list tm" where
  "mergesort_tm f [] =1 return []"
| "mergesort_tm f [x] =1 return [x]"
| "mergesort_tm f xs =1 (
    do {
      n <- length_tm xs;
      (xsl, xsr) <- split_at_tm (n div 2) xs;
      l <- mergesort_tm f xsl;
      r <- mergesort_tm f xsr;
      merge_tm f l r
    }
  )"
  by pat_completeness auto
termination mergesort_tm
  by (relation "Wellfounded.measure (λ(_, xs). length xs)")
     (auto simp add: length_eq_val_length_tm split_at_eq_val_split_at_tm)

fun mergesort :: "('b  'a::linorder)  'b list  'b list" where
  "mergesort f [] = []"
| "mergesort f [x] = [x]"
| "mergesort f xs = ( 
    let n = length xs div 2 in
    let (l, r) = split_at n xs in
    merge f (mergesort f l) (mergesort f r)
  )"

declare split_at_take_drop_conv [simp del]

lemma mergesort_eq_val_mergesort_tm:
  "val (mergesort_tm f xs) = mergesort f xs"
  by (induction f xs rule: mergesort.induct)
     (auto simp add: length_eq_val_length_tm split_at_eq_val_split_at_tm merge_eq_val_merge_tm split: prod.split)

lemma sorted_wrt_mergesort:
  "sorted_wrt (λx y. f x  f y) (mergesort f xs)"
  by (induction f xs rule: mergesort.induct) (auto simp: split_at_take_drop_conv sorted_merge)

lemma set_mergesort:
  "set (mergesort f xs) = set xs"
  by (induction f xs rule: mergesort.induct)
     (simp_all add: set_merge split_at_take_drop_conv, metis list.simps(15) set_take_drop)

lemma length_mergesort:
  "length (mergesort f xs) = length xs"
  by (induction f xs rule: mergesort.induct) (auto simp: length_merge split_at_take_drop_conv)

lemma distinct_mergesort:
  "distinct xs  distinct (mergesort f xs)"
proof (induction f xs rule: mergesort.induct)
  case (3 f x y xs)
  let ?xs' = "x # y # xs"
  obtain l r where lr_def: "(l, r) = split_at (length ?xs' div 2) ?xs'"
    by (metis surj_pair)
  have "distinct l" "distinct r"
    using "3.prems" split_at_take_drop_conv distinct_take distinct_drop lr_def by (metis prod.sel)+
  hence "distinct (mergesort f l)" "distinct (mergesort f r)"
    using "3.IH" lr_def by auto
  moreover have "set l  set r = {}"
    using "3.prems" split_at_take_drop_conv lr_def by (metis append_take_drop_id distinct_append prod.sel)
  ultimately show ?case
    using lr_def by (auto simp: distinct_merge set_mergesort split: prod.splits)
qed auto

lemmas mergesort = sorted_wrt_mergesort set_mergesort length_mergesort distinct_mergesort

lemma sorted_fst_take_less_hd_drop:
  assumes "sorted_fst ps" "n < length ps"
  shows "p  set (take n ps). fst p  fst (hd (drop n ps))"
  using assms sorted_wrt_take_less_hd_drop[of "λp0 p1. fst p0  fst p1"] sorted_fst_def by fastforce

lemma sorted_fst_hd_drop_less_drop:
  assumes "sorted_fst ps"
  shows "p  set (drop n ps). fst (hd (drop n ps))  fst p"
  using assms sorted_wrt_hd_drop_less_drop[of "λp0 p1. fst p0  fst p1"] sorted_fst_def by fastforce

subsubsection "Time Complexity Proof"

lemma time_merge_tm:
  "time (merge_tm f xs ys)  length xs + length ys + 1"
  by (induction f xs ys rule: merge_tm.induct) (auto simp: time_simps)

function mergesort_recurrence :: "nat  real" where
  "mergesort_recurrence 0 = 1"
| "mergesort_recurrence 1 = 1"
| "2  n  mergesort_recurrence n = 4 + 3 * n + mergesort_recurrence (nat real n / 2) + 
    mergesort_recurrence (nat real n / 2)"
  by force simp_all
termination by akra_bazzi_termination simp_all

lemma mergesort_recurrence_nonneg[simp]:
  "0  mergesort_recurrence n"
  by (induction n rule: mergesort_recurrence.induct) (auto simp del: One_nat_def)

lemma time_mergesort_conv_mergesort_recurrence:
  "time (mergesort_tm f xs)  mergesort_recurrence (length xs)"
proof (induction f xs rule: mergesort_tm.induct)
  case (1 f)
  thus ?case by (auto simp: time_simps)
next
  case (2 f x)
  thus ?case using mergesort_recurrence.simps(2) by (auto simp: time_simps)
next
  case (3 f x y xs')

  define xs where "xs = x # y # xs'"
  define n where "n = length xs"
  obtain l r where lr_def: "(l, r) = split_at (n div 2) xs"
    using prod.collapse by blast
  define l' where "l' = mergesort f l"
  define r' where "r' = mergesort f r"
  note defs = xs_def n_def lr_def l'_def r'_def

  have IHL: "time (mergesort_tm f l)  mergesort_recurrence (length l)"
    using defs "3.IH"(1) by (auto simp: length_eq_val_length_tm split_at_eq_val_split_at_tm)
  have IHR: "time (mergesort_tm f r)  mergesort_recurrence (length r)"
    using defs "3.IH"(2) by (auto simp: length_eq_val_length_tm split_at_eq_val_split_at_tm)

  have *: "length l = n div 2" "length r = n - n div 2"
    using defs by (auto simp: split_at_take_drop_conv)
  hence "(nat real n / 2) = length l" "(nat real n / 2) = length r"
    by linarith+
  hence IH: "time (mergesort_tm f l)  mergesort_recurrence (nat real n / 2)"
            "time (mergesort_tm f r)  mergesort_recurrence (nat real n / 2)"
    using IHL IHR by simp_all

  have "n = length l + length r"
    using * by linarith
  hence "time (merge_tm f l' r')  n + 1"
    using time_merge_tm defs by (metis length_mergesort)
  
  have "time (mergesort_tm f xs) = 1 + time (length_tm xs) + time (split_at_tm (n div 2) xs) + 
          time (mergesort_tm f l) + time (mergesort_tm f r) + time (merge_tm f l' r')"
    using defs by (auto simp add: time_simps length_eq_val_length_tm  mergesort_eq_val_mergesort_tm 
                                  split_at_eq_val_split_at_tm 
                        split: prod.split)
  also have "...  4 + 3 * n + time (mergesort_tm f l) + time (mergesort_tm f r)"
    using time_length_tm[of xs] time_split_at_tm[of "n div 2" xs] n_def time (merge_tm f l' r')  n + 1 by simp
  also have "...  4 + 3 * n + mergesort_recurrence (nat real n / 2) + mergesort_recurrence (nat real n / 2)"
    using IH by simp
  also have "... = mergesort_recurrence n"
    using defs by simp
  finally show ?case
    using defs by simp
qed

theorem mergesort_recurrence:
  "mergesort_recurrence  Θ(λn. n * ln n)"
  by (master_theorem) auto

theorem time_mergesort_tm_bigo:
  "(λxs. time (mergesort_tm f xs))  O[length going_to at_top]((λn. n * ln n) o length)"
proof -
  have 0: "xs. time (mergesort_tm f xs)  (mergesort_recurrence o length) xs"
    unfolding comp_def using time_mergesort_conv_mergesort_recurrence by blast
  show ?thesis
    using bigo_measure_trans[OF 0] by (simp add: bigthetaD1 mergesort_recurrence)
qed

subsubsection "Code Export"

lemma merge_xs_Nil[simp]:
  "merge f xs [] = xs"
  by (cases xs) auto

fun merge_it' :: "('b  'a::linorder)  'b list  'b list  'b list  'b list" where
  "merge_it' f acc [] [] = rev acc"
| "merge_it' f acc (x#xs) [] = merge_it' f (x#acc) xs []"
| "merge_it' f acc [] (y#ys) = merge_it' f (y#acc) ys []"
| "merge_it' f acc (x#xs) (y#ys) = (
    if f x  f y then
      merge_it' f (x#acc) xs (y#ys)
    else
      merge_it' f (y#acc) (x#xs) ys
  )"

definition merge_it :: "('b  'a::linorder)  'b list  'b list  'b list" where
  "merge_it f xs ys = merge_it' f [] xs ys"

lemma merge_conv_merge_it':
  "rev acc @ merge f xs ys = merge_it' f acc xs ys"
  by (induction f acc xs ys rule: merge_it'.induct) auto

lemma merge_conv_merge_it[code_unfold]:
  "merge f xs ys = merge_it f xs ys"
  unfolding merge_it_def using merge_conv_merge_it' rev.simps(1) append_Nil by metis


subsection "Minimal Distance"

definition sparse :: "real  point set  bool" where
  "sparse δ ps  (p0  ps. p1  ps. p0  p1  δ  dist p0 p1)"

lemma sparse_identity:
  assumes "sparse δ (set ps)" "p  set ps. δ  dist p0 p"
  shows "sparse δ (set (p0 # ps))"
  using assms by (simp add: dist_commute sparse_def)

lemma sparse_update:
  assumes "sparse δ (set ps)"
  assumes "dist p0 p1  δ" "p  set ps. dist p0 p1  dist p0 p"
  shows "sparse (dist p0 p1) (set (p0 # ps))"
  using assms by (auto simp: dist_commute sparse_def, force+)

lemma sparse_mono:
  "sparse Δ P  δ  Δ  sparse δ P"
  unfolding sparse_def by fastforce


subsection "Distance"

lemma dist_transform:
  fixes p :: point and δ :: real and l :: int
  shows "dist p (l, snd p) < δ  l - δ < fst p  fst p < l + δ"
proof -
  have "dist p (l, snd p) = sqrt ((real_of_int (fst p) - l)2)"
    by (auto simp add: dist_prod_def dist_real_def prod.case_eq_if)
  thus ?thesis
    by auto
qed

fun dist_code :: "point  point  int" where
  "dist_code p0 p1 = (fst p0 - fst p1)2 + (snd p0 - snd p1)2"

lemma dist_eq_sqrt_dist_code:
  fixes p0 :: point
  shows "dist p0 p1 = sqrt (dist_code p0 p1)"
  by (auto simp: dist_prod_def dist_real_def split: prod.splits) 

lemma dist_eq_dist_code_lt:
  fixes p0 :: point
  shows "dist p0 p1 < dist p2 p3  dist_code p0 p1 < dist_code p2 p3"
  using dist_eq_sqrt_dist_code real_sqrt_less_iff by presburger

lemma dist_eq_dist_code_le:
  fixes p0 :: point
  shows "dist p0 p1  dist p2 p3  dist_code p0 p1  dist_code p2 p3"
  using dist_eq_sqrt_dist_code real_sqrt_le_iff by presburger

lemma dist_eq_dist_code_abs_lt:
  fixes p0 :: point
  shows "¦c¦ < dist p0 p1  c2 < dist_code p0 p1"
  using dist_eq_sqrt_dist_code
  by (metis of_int_less_of_int_power_cancel_iff real_sqrt_abs real_sqrt_less_iff)

lemma dist_eq_dist_code_abs_le:
  fixes p0 :: point
  shows "dist p0 p1  ¦c¦  dist_code p0 p1  c2"
  using dist_eq_sqrt_dist_code
  by (metis of_int_power_le_of_int_cancel_iff real_sqrt_abs real_sqrt_le_iff)

lemma dist_fst_abs:
  fixes p :: point and l :: int
  shows "dist p (l, snd p) = ¦fst p - l¦"
proof -
  have "dist p (l, snd p) = sqrt ((real_of_int (fst p) - l)2)"
    by (simp add: dist_prod_def dist_real_def prod.case_eq_if)
  thus ?thesis
    by simp
qed

declare dist_code.simps [simp del]


subsection "Brute Force Closest Pair Algorithm"

subsubsection "Functional Correctness Proof"

fun find_closest_bf_tm :: "point  point list  point tm" where
  "find_closest_bf_tm _ [] =1 return undefined"
| "find_closest_bf_tm _ [p] =1 return p"
| "find_closest_bf_tm p (p0 # ps) =1 (
    do {
      p1 <- find_closest_bf_tm p ps;
      if dist p p0 < dist p p1 then
        return p0
      else
        return p1
    }
  )"

fun find_closest_bf :: "point  point list  point" where
  "find_closest_bf _ [] = undefined"
| "find_closest_bf _ [p] = p"
| "find_closest_bf p (p0 # ps) = (
    let p1 = find_closest_bf p ps in
    if dist p p0 < dist p p1 then
      p0
    else
      p1
  )"

lemma find_closest_bf_eq_val_find_closest_bf_tm:
  "val (find_closest_bf_tm p ps) = find_closest_bf p ps"
  by (induction p ps rule: find_closest_bf.induct) (auto simp: Let_def)

lemma find_closest_bf_set:
  "0 < length ps  find_closest_bf p ps  set ps"
  by (induction p ps rule: find_closest_bf.induct)
     (auto simp: Let_def split: prod.splits if_splits)

lemma find_closest_bf_dist:
  "q  set ps. dist p (find_closest_bf p ps)  dist p q"
  by (induction p ps rule: find_closest_bf.induct)
     (auto split: prod.splits)

fun closest_pair_bf_tm :: "point list  (point × point) tm" where
  "closest_pair_bf_tm [] =1 return undefined"
| "closest_pair_bf_tm [_] =1 return undefined"
| "closest_pair_bf_tm [p0, p1] =1 return (p0, p1)"
| "closest_pair_bf_tm (p0 # ps) =1 (
    do {
      (c0::point, c1::point) <- closest_pair_bf_tm ps;
      p1 <- find_closest_bf_tm p0 ps;
      if dist c0 c1  dist p0 p1 then
        return (c0, c1)
      else
        return (p0, p1)
    }
  )"

fun closest_pair_bf :: "point list  (point * point)" where
  "closest_pair_bf [] = undefined"
| "closest_pair_bf [_] = undefined"
| "closest_pair_bf [p0, p1] = (p0, p1)"
| "closest_pair_bf (p0 # ps) = (
    let (c0, c1) = closest_pair_bf ps in
    let p1 = find_closest_bf p0 ps in
    if dist c0 c1  dist p0 p1 then
      (c0, c1)
    else
      (p0, p1) 
  )"

lemma closest_pair_bf_eq_val_closest_pair_bf_tm:
  "val (closest_pair_bf_tm ps) = closest_pair_bf ps"
  by (induction ps rule: closest_pair_bf.induct) 
     (auto simp: Let_def find_closest_bf_eq_val_find_closest_bf_tm split: prod.split)

lemma closest_pair_bf_c0:
  "1 < length ps  (c0, c1) = closest_pair_bf ps  c0  set ps"
  by (induction ps arbitrary: c0 c1 rule: closest_pair_bf.induct)
     (auto simp: Let_def find_closest_bf_set split: if_splits prod.splits)

lemma closest_pair_bf_c1:
  "1 < length ps  (c0, c1) = closest_pair_bf ps  c1  set ps" 
proof (induction ps arbitrary: c0 c1 rule: closest_pair_bf.induct)
  case (4 p0 p2 p3 ps)
  let ?ps = "p2 # p3 # ps"
  obtain c0 c1 where c0_def: "(c0, c1) = closest_pair_bf ?ps"
    using prod.collapse by blast
  define p1 where p1_def: "p1 = find_closest_bf p0 ?ps"
  note defs = c0_def p1_def
  have "c1  set ?ps"
    using "4.IH" defs by simp
  moreover have "p1  set ?ps"
    using find_closest_bf_set defs by blast
  ultimately show ?case
    using "4.prems"(2) defs by (auto simp: Let_def split: prod.splits if_splits)
qed auto

lemma closest_pair_bf_c0_ne_c1:
  "1 < length ps  distinct ps  (c0, c1) = closest_pair_bf ps  c0  c1"
proof (induction ps arbitrary: c0 c1 rule: closest_pair_bf.induct)
  case (4 p0 p2 p3 ps)
  let ?ps = "p2 # p3 # ps"
  obtain c0 c1 where c0_def: "(c0, c1) = closest_pair_bf ?ps"
    using prod.collapse by blast
  define p1 where p1_def: "p1 = find_closest_bf p0 ?ps"
  note defs = c0_def p1_def
  have "c0  c1"
    using "4.IH" "4.prems"(2) defs by simp
  moreover have "p0  p1"
    using find_closest_bf_set "4.prems"(2) defs
    by (metis distinct.simps(2) length_pos_if_in_set list.set_intros(1))
  ultimately show ?case
    using "4.prems"(3) defs by (auto simp: Let_def split: prod.splits if_splits)
qed auto

lemmas closest_pair_bf_c0_c1 = closest_pair_bf_c0 closest_pair_bf_c1 closest_pair_bf_c0_ne_c1

lemma closest_pair_bf_dist:
  assumes "1 < length ps" "(c0, c1) = closest_pair_bf ps"
  shows "sparse (dist c0 c1) (set ps)"
  using assms
proof (induction ps arbitrary: c0 c1 rule: closest_pair_bf.induct)
  case (4 p0 p2 p3 ps)
  let ?ps = "p2 # p3 # ps"
  obtain c0 c1 where c0_def: "(c0, c1) = closest_pair_bf ?ps"
    using prod.collapse by blast
  define p1 where p1_def: "p1 = find_closest_bf p0 ?ps"
  note defs = c0_def p1_def
  hence IH: "sparse (dist c0 c1) (set ?ps)"
    using 4 c0_def by simp
  have *: "p  set ?ps. (dist p0 p1)  dist p0 p"
    using find_closest_bf_dist defs by blast
  show ?case
  proof (cases "dist c0 c1  dist p0 p1")
    case True
    hence "p  set ?ps. dist c0 c1  dist p0 p"
      using * by auto
    hence "sparse (dist c0 c1) (set (p0 # ?ps))"
      using sparse_identity IH by blast
    thus ?thesis
      using True "4.prems" defs by (auto split: prod.splits)
  next
    case False
    hence "sparse (dist p0 p1) (set (p0 # ?ps))"
      using sparse_update[of "dist c0 c1" ?ps p0 p1] IH * defs by argo
    thus ?thesis
      using False "4.prems" defs by (auto split: prod.splits)
  qed
qed (auto simp: dist_commute sparse_def)

subsubsection "Time Complexity Proof"

lemma time_find_closest_bf_tm:
  "time (find_closest_bf_tm p ps)  length ps + 1"
  by (induction p ps rule: find_closest_bf_tm.induct) (auto simp: time_simps)

lemma time_closest_pair_bf_tm:
  "time (closest_pair_bf_tm ps)  length ps * length ps + 1"
proof (induction ps rule: closest_pair_bf_tm.induct)
  case (4 p0 p2 p3 ps)
  let ?ps = "p2 # p3 # ps"
  have "time (closest_pair_bf_tm (p0 # ?ps)) = 1 + time (find_closest_bf_tm p0 ?ps) + time (closest_pair_bf_tm ?ps)"
    by (auto simp: time_simps split: prod.split)
  also have "...  2 + length ?ps + time (closest_pair_bf_tm ?ps)"
    using time_find_closest_bf_tm[of p0 ?ps] by simp
  also have "...  2 + length ?ps + length ?ps * length ?ps + 1"
    using "4.IH" by simp
  also have "...  length (p0 # ?ps) * length (p0 # ?ps) + 1"
    by auto
  finally show ?case
    by blast
qed (auto simp: time_simps)

subsubsection "Code Export"

fun find_closest_bf_code :: "point  point list  (int * point)" where
  "find_closest_bf_code p [] = undefined"
| "find_closest_bf_code p [p0] = (dist_code p p0, p0)"
| "find_closest_bf_code p (p0 # ps) = (
    let (δ1, p1) = find_closest_bf_code p ps in
    let δ0 = dist_code p p0 in
    if δ0 < δ1 then
      (δ0, p0)
    else
      (δ1, p1)
  )"

lemma find_closest_bf_code_dist_eq:
  "0 < length ps  (δ, c) = find_closest_bf_code p ps  δ = dist_code p c"
  by (induction p ps rule: find_closest_bf_code.induct)
     (auto simp: Let_def split: prod.splits if_splits)

lemma find_closest_bf_code_eq:
  "0 < length ps  c = find_closest_bf p ps  (δ', c') = find_closest_bf_code p ps  c = c'"
proof (induction p ps arbitrary: c δ' c' rule: find_closest_bf.induct)
  case (3 p p0 p2 ps)
  define δ0 δ0' where δ0_def: "δ0 = dist p p0" "δ0' = dist_code p p0"
  obtain δ1 p1 δ1' p1' where δ1_def: "δ1 = dist p p1" "p1 = find_closest_bf p (p2 # ps)"
    "(δ1', p1') = find_closest_bf_code p (p2 # ps)"
    using prod.collapse by blast+
  note defs = δ0_def δ1_def
  have *: "p1 = p1'"
    using "3.IH" defs by simp
  hence "δ0 < δ1  δ0' < δ1'"
    using find_closest_bf_code_dist_eq[of "p2 # ps" δ1' p1' p]
          dist_eq_dist_code_lt defs
    by simp
  thus ?case
    using "3.prems"(2,3) * defs by (auto split: prod.splits)
qed auto

declare find_closest_bf_code.simps [simp del]

fun closest_pair_bf_code :: "point list  (int * point * point)" where
  "closest_pair_bf_code [] = undefined"
| "closest_pair_bf_code [p0] = undefined"
| "closest_pair_bf_code [p0, p1] = (dist_code p0 p1, p0, p1)"
| "closest_pair_bf_code (p0 # ps) = (
    let (δc, c0, c1) = closest_pair_bf_code ps in
    let (δp, p1) = find_closest_bf_code p0 ps in
    if δc  δp then
      (δc, c0, c1)
    else
      (δp, p0, p1) 
  )"

lemma closest_pair_bf_code_dist_eq:
  "1 < length ps  (δ, c0, c1) = closest_pair_bf_code ps  δ = dist_code c0 c1"
proof (induction ps arbitrary: δ c0 c1 rule: closest_pair_bf_code.induct)
  case (4 p0 p2 p3 ps)
  let ?ps = "p2 # p3 # ps"
  obtain δc c0 c1 where δc_def: "(δc, c0, c1) = closest_pair_bf_code ?ps"
    by (metis prod_cases3)
  obtain δp p1 where δp_def: "(δp, p1) = find_closest_bf_code p0 ?ps"
    using prod.collapse by blast
  note defs = δc_def δp_def
  have "δc = dist_code c0 c1"
    using "4.IH" defs by simp
  moreover have "δp = dist_code p0 p1"
    using find_closest_bf_code_dist_eq defs by blast
  ultimately show ?case
    using "4.prems"(2) defs by (auto split: prod.splits if_splits)
qed auto

lemma closest_pair_bf_code_eq:
  assumes "1 < length ps" 
  assumes "(c0, c1) = closest_pair_bf ps" "(δ', c0', c1') = closest_pair_bf_code ps"
  shows "c0 = c0'  c1 = c1'"
  using assms
proof (induction ps arbitrary: c0 c1 δ' c0' c1' rule: closest_pair_bf_code.induct)
  case (4 p0 p2 p3 ps)
  let ?ps = "p2 # p3 # ps"
  obtain c0 c1 δc' c0' c1' where δc_def: "(c0, c1) = closest_pair_bf ?ps"
    "(δc', c0', c1') = closest_pair_bf_code ?ps"
    by (metis prod_cases3)
  obtain p1 δp' p1' where δp_def: "p1 = find_closest_bf p0 ?ps"
    "(δp', p1') = find_closest_bf_code p0 ?ps"
    using prod.collapse by blast
  note defs = δc_def δp_def
  have A: "c0 = c0'  c1 = c1'"
    using "4.IH" defs by simp
  moreover have B: "p1 = p1'"
    using find_closest_bf_code_eq defs by blast
  moreover have "δc' = dist_code c0' c1'"
    using defs closest_pair_bf_code_dist_eq[of ?ps] by simp
  moreover have "δp' = dist_code p0 p1'"
    using defs find_closest_bf_code_dist_eq by blast
  ultimately have "dist c0 c1  dist p0 p1  δc'  δp'"
    by (simp add: dist_eq_dist_code_le)
  thus ?case
    using "4.prems"(2,3) defs A B by (auto simp: Let_def split: prod.splits)
qed auto


subsection "Geometry"

subsubsection "Band Filter"

lemma set_band_filter_aux:
  fixes δ :: real and ps :: "point list"
  assumes "p0  psL" "p1  psR" "p0  p1" "dist p0 p1 < δ" "set ps = psL  psR"
  assumes "p  psL. fst p  l" "p  psR. l  fst p"
  assumes "ps' = filter (λp. l - δ < fst p  fst p < l + δ) ps"
  shows "p0  set ps'  p1  set ps'"
proof (rule ccontr)
  assume "¬ (p0  set ps'  p1  set ps')"
  then consider (A) "p0  set ps'  p1  set ps'"
              | (B) "p0  set ps'  p1  set ps'"
              | (C) "p0  set ps'  p1  set ps'"
    by blast
  thus False
  proof cases
    case A
    hence "fst p0  l - δ  l + δ  fst p0" "fst p1  l - δ  l + δ  fst p1"
      using assms(1,2,5,8) by auto
    hence "fst p0  l - δ" "l + δ  fst p1"
      using assms(1,2,6,7) by force+
    hence "δ  dist (fst p0) (fst p1)"
      using dist_real_def by simp
    hence "δ  dist p0 p1"
      using dist_fst_le[of p0 p1] by (auto split: prod.splits)
    then show ?thesis
      using assms(4) by fastforce
  next
    case B
    hence "fst p1  l - δ  l + δ  fst p1"
      using assms(2,5,8) by auto
    hence "l + δ  fst p1"
      using assms(2,7) by auto
    moreover have "fst p0  l"
      using assms(1,6) by simp
    ultimately have "δ  dist (fst p0) (fst p1)"
      using dist_real_def by simp
    hence "δ  dist p0 p1"
      using dist_fst_le[of p0 p1] less_le_trans by (auto split: prod.splits)
    thus ?thesis
      using assms(4) by simp
  next
    case C
    hence "fst p0  l - δ  l + δ  fst p0"
      using assms(1,2,5,8) by auto
    hence "fst p0  l - δ"
      using assms(1,6) by auto
    moreover have "l  fst p1"
      using assms(2,7) by simp
    ultimately have "δ  dist (fst p0) (fst p1)"
      using dist_real_def by simp
    hence "δ  dist p0 p1"
      using dist_fst_le[of p0 p1] less_le_trans by (auto split: prod.splits)
    thus ?thesis
      using assms(4) by simp
  qed
qed
  
lemma set_band_filter:
  fixes δ :: real and ps :: "point list"
  assumes "p0  set ps" "p1  set ps" "p0  p1" "dist p0 p1 < δ" "set ps = psL  psR"
  assumes "sparse δ psL" "sparse δ psR"
  assumes "p  psL. fst p  l" "p  psR. l  fst p"
  assumes "ps' = filter (λp. l - δ < fst p  fst p < l + δ) ps"
  shows "p0  set ps'  p1  set ps'"
proof -
  have "p0  psL  p1  psL" "p0  psR  p1  psR"
    using assms(3,4,6,7) sparse_def by force+
  then consider (A) "p0  psL  p1  psR" | (B) "p0  psR  p1  psL"
    using assms(1,2,5) by auto
  thus ?thesis
  proof cases
    case A
    thus ?thesis
      using set_band_filter_aux assms(3,4,5,8,9,10) by (auto split: prod.splits)
  next
    case B
    moreover have "dist p1 p0 < δ"
      using assms(4) dist_commute by metis
    ultimately show ?thesis
      using set_band_filter_aux assms(3)[symmetric] assms(5,8,9,10) by (auto split: prod.splits)
  qed
qed

subsubsection "2D-Boxes and Points"

lemma cbox_2D: 
  fixes x0 :: real and y0 :: real
  shows "cbox (x0, y0) (x1, y1) = { (x, y). x0  x  x  x1  y0  y  y  y1 }"
  by fastforce

lemma mem_cbox_2D:
  fixes x :: real and y :: real
  shows "x0  x  x  x1  y0  y  y  y1  (x, y)  cbox (x0, y0) (x1, y1)"
  by fastforce

lemma cbox_top_un:
  fixes x0 :: real and y0 :: real
  assumes "y0  y1" "y1  y2"
  shows "cbox (x0, y0) (x1, y1)  cbox (x0, y1) (x1, y2) = cbox (x0, y0) (x1, y2)"
  using assms by auto

lemma cbox_right_un:
  fixes x0 :: real and y0 :: real
  assumes "x0  x1" "x1  x2"
  shows "cbox (x0, y0) (x1, y1)  cbox (x1, y0) (x2, y1) = cbox (x0, y0) (x2, y1)"
  using assms by auto

lemma cbox_max_dist:
  assumes "p0 = (x, y)" "p1 = (x + δ, y + δ)"
  assumes "(x0, y0)  cbox p0 p1" "(x1, y1)  cbox p0 p1" "0  δ"
  shows "dist (x0, y0) (x1, y1)  sqrt 2 * δ"
proof -
  have X: "dist x0 x1  δ"
    using assms dist_real_def by auto
  have Y: "dist y0 y1  δ"
    using assms dist_real_def by auto

  have "dist (x0, y0) (x1, y1) = sqrt ((dist x0 x1)2 + (dist y0 y1)2)"
    using dist_Pair_Pair by auto
  also have "...  sqrt (δ2 + (dist y0 y1)2)"
    using X power_mono by fastforce
  also have "...  sqrt (δ2 + δ2)"
    using Y power_mono by fastforce
  also have "... = sqrt 2 * sqrt (δ2)"
    using real_sqrt_mult by simp
  also have "... = sqrt 2 * δ"
    using assms(5) by simp
  finally show ?thesis .
qed

subsubsection "Pigeonhole Argument"

lemma card_le_1_if_pairwise_eq:
  assumes "x  S. y  S. x = y"
  shows "card S  1"
proof (rule ccontr)
  assume "¬ card S  1"
  hence "2  card S"
    by simp
  then obtain T where *: "T  S  card T = 2"
    using ex_card by metis
  then obtain x y where "x  T  y  T  x  y"
    by (meson card_2_iff')
  then show False
    using * assms by blast
qed

lemma card_Int_if_either_in:
  assumes "x  S. y  S. x = y  x  T  y  T" 
  shows "card (S  T)  1"
proof (rule ccontr)
  assume "¬ (card (S  T)  1)"
  then obtain x y where *: "x  S  T  y  S  T  x  y"
    by (meson card_le_1_if_pairwise_eq)
  hence "x  T" "y  T"
    by simp_all
  moreover have "x  T  y  T"
    using assms * by auto
  ultimately show False
    by blast
qed

lemma card_Int_Un_le_Sum_card_Int:
  assumes "finite S"
  shows "card (A  S)  (B  S. card (A  B))"
  using assms
proof (induction "card S" arbitrary: S)
  case (Suc n)
  then obtain B T where *: "S = { B }  T" "card T = n" "B  T"
    by (metis card_Suc_eq Suc_eq_plus1 insert_is_Un)
  hence "card (A  S) = card (A  ({ B }  T))"
    by blast
  also have "...  card (A  B) + card (A  T)"
    by (simp add: card_Un_le inf_sup_distrib1)
  also have "...  card (A  B) + (B  T. card (A  B))"
    using Suc * by simp
  also have "...  (B  S. card (A  B))"
    using Suc.prems * by simp
  finally show ?case .
qed simp

lemma pigeonhole:
  assumes "finite T" "S  T" "card T < card S"
  shows "x  S. y  S. X  T. x  y  x  X  y  X"
proof (rule ccontr)
  assume "¬ (x  S. y  S. X  T. x  y  x  X  y  X)"
  hence *: "X  T. card (S  X)  1"
    using card_Int_if_either_in by metis
  have "card T < card (S  T)"
    using Int_absorb2 assms by fastforce
  also have "...  (X  T. card (S  X))"
    using assms(1) card_Int_Un_le_Sum_card_Int by blast
  also have "...  card T"
    using * sum_mono by fastforce
  finally show False by simp
qed

subsubsection "Delta Sparse Points within a Square"

lemma max_points_square:
  assumes "p  ps. p  cbox (x, y) (x + δ, y + δ)" "sparse δ ps" "0  δ"
  shows "card ps  4"
proof (cases "δ = 0")
  case True
  hence "{ (x, y) } = cbox (x, y) (x + δ, y + δ)"
    using cbox_def by simp
  hence "p  ps. p = (x, y)"
    using assms(1) by blast
  hence "p  ps. q  ps. p = q"
    apply (auto split: prod.splits) by (metis of_int_eq_iff)+
  thus ?thesis
    using card_le_1_if_pairwise_eq by force
next
  case False
  hence δ: "0 < δ"
    using assms(3) by simp
  show ?thesis
  proof (rule ccontr)
    assume A: "¬ (card ps  4)"
    define PS where PS_def: "PS = (λ(x, y). (real_of_int x, real_of_int y)) ` ps"
    have "inj_on (λ(x, y). (real_of_int x, real_of_int y)) ps"
      using inj_on_def by fastforce
    hence *: "¬ (card PS  4)"
      using A PS_def by (simp add: card_image)

    let ?x' = "x + δ / 2"
    let ?y' = "y + δ / 2"

    let ?ll = "cbox ( x ,  y ) (?x'   , ?y'   )"
    let ?lu = "cbox ( x , ?y') (?x'   ,  y + δ)"
    let ?rl = "cbox (?x',  y ) ( x + δ, ?y'   )"
    let ?ru = "cbox (?x', ?y') ( x + δ,  y + δ)"

    let ?sq = "{ ?ll, ?lu, ?rl, ?ru }"

    have card_le_4: "card ?sq  4"
      by (simp add: card_insert_le_m1)

    have "cbox (x, y) (?x', y + δ) = ?ll  ?lu"
      using cbox_top_un assms(3) by auto
    moreover have "cbox (?x', y) (x + δ, y + δ) = ?rl  ?ru"
      using cbox_top_un assms(3) by auto
    moreover have "cbox (x, y) (?x', y + δ)  cbox (?x', y) (x + δ, y + δ) = cbox (x, y) (x + δ, y + δ)"
      using cbox_right_un assms(3) by simp
    ultimately have "?ll  ?lu  ?rl  ?ru = cbox (x, y) (x + δ, y + δ)"
      by blast

    hence "PS  (?sq)"
      using assms(1) PS_def by (auto split: prod.splits)
    moreover have "card ?sq < card PS"
      using * card_insert_le_m1 card_le_4 by linarith
    moreover have "finite ?sq"
      by simp
    ultimately have "p0  PS. p1  PS. S  ?sq. (p0  p1  p0  S  p1  S)"
      using pigeonhole[of ?sq PS] by blast
    then obtain S p0 p1 where #: "p0  PS" "p1  PS" "S  ?sq" "p0  p1" "p0  S" "p1  S"
      by blast

    have D: "0  δ / 2"
      using assms(3) by simp
    have LL: "p0  ?ll. p1  ?ll. dist p0 p1  sqrt 2 * (δ / 2)"
      using cbox_max_dist[of "(x, y)" x y "(?x', ?y')" "δ / 2"] D by auto
    have LU: "p0  ?lu. p1  ?lu. dist p0 p1  sqrt 2 * (δ / 2)"
      using cbox_max_dist[of "(x, ?y')" x ?y' "(?x', y + δ)" "δ / 2"] D by auto
    have RL: "p0  ?rl. p1  ?rl. dist p0 p1  sqrt 2 * (δ / 2)"
      using cbox_max_dist[of "(?x', y)" ?x' y "(x + δ, ?y')" "δ / 2"] D by auto
    have RU: "p0  ?ru. p1  ?ru. dist p0 p1  sqrt 2 * (δ / 2)"
      using cbox_max_dist[of "(?x', ?y')" ?x' ?y' "(x + δ, y + δ)" "δ / 2"] D by auto

    have "p0  S. p1  S. dist p0 p1  sqrt 2 * (δ / 2)"
      using # LL LU RL RU by blast
    hence "dist p0 p1  (sqrt 2 / 2) * δ"
      using # by simp
    moreover have "(sqrt 2 / 2) * δ < δ"
      using sqrt2_less_2 δ by simp
    ultimately have "dist p0 p1 < δ"
      by simp
    moreover have "δ  dist p0 p1"
      using assms(2) # sparse_def PS_def by auto
    ultimately show False
      by simp
  qed
qed

end