Theory Executable_Permutations

section ‹Permutations as Products of Disjoint Cycles›

theory Executable_Permutations
imports
  "HOL-Combinatorics.Permutations"
  Graph_Theory.Auxiliary
  List_Aux
begin

subsection ‹Cyclic Permutations›

definition list_succ :: "'a list ⇒ 'a ⇒ 'a" where
  "list_succ xs x = (if x ∈ set xs then xs ! ((index xs x + 1) mod length xs) else x)"

text ‹
  We demonstrate the functions on the following simple lemmas

  @{lemma "list_succ [1 :: int, 2, 3] 1 = 2" by code_simp}
  @{lemma "list_succ [1 :: int, 2, 3] 2 = 3" by code_simp}
  @{lemma "list_succ [1 :: int, 2, 3] 3 = 1" by code_simp}
›

lemma list_succ_altdef:
  "list_succ xs x = (let n = index xs x in if n + 1 = length xs then xs ! 0 else if n + 1 < length xs then xs ! (n + 1) else x)"
  using index_le_size[of xs x] unfolding list_succ_def index_less_size_conv[symmetric] by (auto simp: Let_def)

lemma list_succ_Nil:
  "list_succ [] = id"
  by (simp add: list_succ_def fun_eq_iff)

lemma list_succ_singleton:
  "list_succ [x] = list_succ []"
  by (simp add: fun_eq_iff list_succ_def)

lemma list_succ_short:
  assumes "length xs < 2" shows "list_succ xs = id"
  using assms
  by (cases xs) (rename_tac [2] y ys, case_tac [2] ys, auto simp: list_succ_Nil list_succ_singleton)

lemma list_succ_simps:
  "index xs x + 1 = length xs ⟹ list_succ xs x = xs ! 0"
  "index xs x + 1 < length xs ⟹ list_succ xs x = xs ! (index xs x + 1)"
  "length xs ≤ index xs x ⟹ list_succ xs x = x"
  by (auto simp: list_succ_altdef)

lemma list_succ_not_in:
  assumes "x ∉ set xs" shows "list_succ xs x = x"
  using assms by (auto simp: list_succ_def)

lemma list_succ_list_succ_rev:
  assumes "distinct xs" shows "list_succ (rev xs) (list_succ xs x) = x"
proof -
  { assume "index xs x + 1 < length xs"
    moreover then have "length xs - Suc (Suc (length xs - Suc (Suc (index xs x)))) = index xs x"
      by linarith
    ultimately have ?thesis using assms
      by (simp add: list_succ_def index_rev index_nth_id rev_nth)
  }
  moreover
  { assume A: "index xs x + 1 = length xs"
    moreover
    from A have "xs ≠ []" by auto
    moreover
    with A have "last xs = xs ! index xs x" by (cases "length xs") (auto simp: last_conv_nth)
    ultimately
    have ?thesis
      using assms
      by (auto simp add: list_succ_def rev_nth index_rev index_nth_id last_conv_nth)
  }
  moreover
  { assume A: "index xs x ≥ length xs"
    then have "x ∉ set xs" by (metis index_less less_irrefl)
    then have ?thesis by (auto simp: list_succ_def) }
  ultimately show ?thesis 
    by linarith 
qed

lemma inj_list_succ: "distinct xs ⟹ inj (list_succ xs)"
  by (metis injI list_succ_list_succ_rev)

lemma inv_list_succ_eq: "distinct xs ⟹ inv (list_succ xs) = list_succ (rev xs)"
  by (metis distinct_rev inj_imp_inv_eq inj_list_succ list_succ_list_succ_rev)

lemma bij_list_succ: "distinct xs ⟹ bij (list_succ xs)"
  by (metis bij_def inj_list_succ distinct_rev list_succ_list_succ_rev surj_def)

lemma list_succ_permutes:
  assumes "distinct xs" shows "list_succ xs permutes set xs"
  using assms by (auto simp: permutes_conv_has_dom bij_list_succ has_dom_def list_succ_def)

lemma permutation_list_succ:
  assumes "distinct xs" shows "permutation (list_succ xs)"
  using list_succ_permutes[OF assms] by (auto simp: permutation_permutes)

lemma list_succ_nth:
  assumes "distinct xs" "n < length xs" shows "list_succ xs (xs ! n) = xs ! (Suc n mod length xs)"
  using assms by (auto simp: list_succ_def index_nth_id)

lemma list_succ_last[simp]:
  assumes "distinct xs" "xs ≠ []" shows "list_succ xs (last xs) = hd xs"
  using assms by (auto simp: list_succ_def hd_conv_nth)

lemma list_succ_rotate1[simp]:
  assumes "distinct xs" shows "list_succ (rotate1 xs) = list_succ xs"
proof (rule ext)
  fix y show "list_succ (rotate1 xs) y = list_succ xs y"
    using assms
  proof (induct xs)
    case Nil then show ?case by simp
  next
    case (Cons x xs)
    show ?case
    proof (cases "x = y")
      case True
      then have "index (xs @ [y]) y = length xs"
        using ‹distinct (x # xs)› by (simp add: index_append)
      with True show ?thesis by (cases "xs=[]") (auto simp: list_succ_def nth_append)
    next
      case False
      then show ?thesis
        apply (cases "index xs y + 1 < length xs")
        apply (auto simp:list_succ_def index_append nth_append)
        by (metis Suc_lessI index_less_size_conv mod_self nth_Cons_0 nth_append nth_append_length)
    qed
  qed
qed
  
lemma list_succ_rotate[simp]:
  assumes "distinct xs" shows "list_succ (rotate n xs) = list_succ xs"
  using assms by (induct n) auto

lemma list_succ_in_conv:
  "list_succ xs x ∈ set xs ⟷ x ∈ set xs"
unfolding list_succ_def using length_pos_if_in_set by force

lemma list_succ_in_conv1:
  assumes "A ∩ set xs = {}"
  shows "list_succ xs x ∈ A ⟷ x ∈ A"
  by (metis assms disjoint_iff_not_equal list_succ_in_conv list_succ_not_in)

lemma list_succ_commute:
  assumes "set xs ∩ set ys = {}"
  shows "list_succ xs (list_succ ys x) = list_succ ys (list_succ xs x)"
proof -
  have "⋀x. x ∈ set xs ⟹ list_succ ys x = x"
     "⋀x. x ∈ set ys ⟹ list_succ xs x = x"
    using assms by (blast intro: list_succ_not_in)+
  then show ?thesis
    by (cases "x ∈ set xs ∪ set ys") (auto simp: list_succ_in_conv list_succ_not_in)
qed


subsection ‹Arbitrary Permutations›

fun lists_succ :: "'a list list ⇒ 'a ⇒ 'a" where
  "lists_succ [] x = x"
| "lists_succ (xs # xss) x = list_succ xs (lists_succ xss x)"

definition distincts ::  "'a list list ⇒ bool" where
  "distincts xss ≡ distinct xss ∧ (∀xs ∈ set xss. distinct xs ∧ xs ≠ []) ∧ (∀xs ∈ set xss. ∀ys ∈ set xss. xs ≠ ys ⟶ set xs ∩ set ys = {})"

lemma distincts_distinct: "distincts xss ⟹ distinct xss"
  by (auto simp: distincts_def)

lemma distincts_Nil[simp]: "distincts []"
  by (simp add: distincts_def)

lemma distincts_single: "distincts [xs] ⟷ distinct xs ∧ xs ≠ []"
  by (auto simp add: distincts_def)

lemma distincts_Cons: "distincts (xs # xss)
   ⟷ xs ≠ [] ∧ distinct xs ∧ distincts xss ∧ (set xs ∩ (⋃ys ∈ set xss. set ys)) = {}" (is "?L ⟷ ?R")
proof 
  assume ?L then show ?R by (auto simp: distincts_def)
next
  assume ?R
  then have "distinct (xs # xss)"
    apply (auto simp: disjoint_iff_not_equal distincts_distinct)
    apply (metis length_greater_0_conv nth_mem)
    done
  moreover
  from ‹?R› have "∀xs ∈ set (xs # xss). distinct xs ∧ xs ≠ []"
    by (auto simp: distincts_def)
  moreover
  from ‹?R› have "∀xs' ∈ set (xs # xss). ∀ys ∈ set (xs # xss). xs' ≠ ys ⟶ set xs' ∩ set ys = {}"
    by (simp add: distincts_def) blast
  ultimately show ?L unfolding distincts_def by (intro conjI)
qed

lemma distincts_Cons': "distincts (xs # xss)
   ⟷ xs ≠ [] ∧ distinct xs ∧ distincts xss ∧ (∀ys ∈ set xss. set xs ∩ set ys = {})" (is "?L ⟷ ?R")
 unfolding distincts_Cons by blast

lemma distincts_rev:
  "distincts (map rev xss) ⟷ distincts xss"
  by (simp add: distincts_def distinct_map)

lemma length_distincts:
  assumes "distincts xss"
  shows "length xss = card (set ` set xss)"
  using assms
proof (induct xss)
  case Nil then show ?case by simp
next
  case (Cons xs xss)
  then have "set xs ∉ set ` set xss"
    using equals0I[of "set xs"] by (auto simp: distincts_Cons disjoint_iff_not_equal )
  with Cons show ?case by (auto simp add: distincts_Cons)
qed

lemma distincts_remove1: "distincts xss ⟹ distincts (remove1 xs xss)"
  by (auto simp: distincts_def)

lemma distinct_Cons_remove1:
  "x ∈ set xs ⟹ distinct (x # remove1 x xs) = distinct xs"
  by (induct xs) auto

lemma set_Cons_remove1:
  "x ∈ set xs ⟹ set (x # remove1 x xs) = set xs"
  by (induct xs) auto

lemma distincts_Cons_remove1:
  "xs ∈ set xss ⟹ distincts (xs # remove1 xs xss) = distincts xss"
  by (simp only: distinct_Cons_remove1 set_Cons_remove1 distincts_def)

lemma distincts_inj_on_set:
  assumes "distincts xss" shows "inj_on set (set xss)"
  by (rule inj_onI) (metis assms distincts_def inf.idem set_empty)

lemma distincts_distinct_set:
  assumes "distincts xss" shows "distinct (map set xss)"
  using assms by (auto simp: distinct_map distincts_distinct distincts_inj_on_set)

lemma distincts_distinct_nth:
  assumes "distincts xss" "n < length xss" shows "distinct (xss ! n)"
  using assms by (auto simp: distincts_def)

lemma lists_succ_not_in:
  assumes "x ∉ (⋃xs∈set xss. set xs)" shows "lists_succ xss x = x"
  using assms by (induct xss) (auto simp: list_succ_not_in)

lemma lists_succ_in_conv:
  "lists_succ xss x ∈ (⋃xs∈set xss. set xs) ⟷ x ∈ (⋃xs∈set xss. set xs)"
  by (induct xss) (auto simp: list_succ_in_conv lists_succ_not_in list_succ_not_in)

lemma lists_succ_in_conv1:
  assumes "A ∩ (⋃xs∈set xss. set xs) = {}"
  shows "lists_succ xss x ∈ A ⟷ x ∈ A"
  by (metis Int_iff assms emptyE lists_succ_in_conv lists_succ_not_in)

lemma lists_succ_Cons_pf: "lists_succ (xs # xss) = list_succ xs o lists_succ xss"
  by auto

lemma lists_succ_Nil_pf: "lists_succ [] = id"
  by (simp add: fun_eq_iff)

lemmas lists_succ_simps_pf = lists_succ_Cons_pf lists_succ_Nil_pf

lemma lists_succ_permutes:
  assumes "distincts xss"
  shows "lists_succ xss permutes (⋃xs ∈ set xss. set xs)"
  using assms
proof (induction xss)
  case Nil then show ?case by auto
next
  case (Cons xs xss)
  have "list_succ xs permutes (set xs)"
    using Cons by (intro list_succ_permutes) (simp add: distincts_def)
  moreover
  have "lists_succ xss permutes (⋃ys ∈ set xss. set ys)"
    using Cons by (auto simp: Cons distincts_def)
  ultimately show "lists_succ (xs # xss) permutes (⋃ys ∈ set (xs # xss). set ys)"
    using Cons by (auto simp: lists_succ_Cons_pf intro: permutes_compose permutes_subset)
qed

lemma bij_lists_succ: "distincts xss ⟹ bij (lists_succ xss)"
  by (induct xss) (auto simp: lists_succ_simps_pf bij_comp bij_list_succ distincts_Cons)

lemma lists_succ_snoc: "lists_succ (xss @ [xs]) = lists_succ xss o list_succ xs"
  by (induct xss) auto

lemma inv_lists_succ_eq:
  assumes "distincts xss"
  shows "inv (lists_succ xss) = lists_succ (rev (map rev xss))"
proof -
  have *: "⋀f g. inv (λb. f (g b)) = inv (f o g)" by (simp add: o_def)
  have **: "lists_succ [] = id" by auto
  show ?thesis
    using assms by (induct xss) (auto simp: * ** lists_succ_snoc lists_succ_Cons_pf o_inv_distrib
      inv_list_succ_eq distincts_Cons bij_list_succ bij_lists_succ)
qed

lemma lists_succ_remove1:
  assumes "distincts xss" "xs ∈ set xss"
  shows "lists_succ (xs # remove1 xs xss) = lists_succ xss"
  using assms
proof (induct xss)
  case Nil then show ?case by simp
next
  case (Cons ys xss)
  show ?case
  proof cases
    assume "xs = ys" then show ?case by simp
  next
    assume "xs ≠ ys"
    with Cons.prems have inter: "set xs ∩ set ys = {}" and "xs ∈ set xss"
      by (auto simp: distincts_Cons)
    have dists:
        "distincts (xs # remove1 xs xss)"
        "distincts (xs # ys # remove1 xs xss)"
      using ‹distincts (ys # xss)› ‹xs ∈ set xss› by (auto simp: distincts_def)

    have "list_succ xs ∘ (list_succ ys ∘ lists_succ (remove1 xs xss))
        = list_succ ys ∘ (list_succ xs ∘ lists_succ (remove1 xs xss))"
      using inter unfolding fun_eq_iff comp_def
      by (subst list_succ_commute) auto
    also have "… = list_succ ys o (lists_succ (xs # remove1 xs xss))"
      using dists by (simp add: lists_succ_Cons_pf distincts_Cons)
    also have "… = list_succ ys o lists_succ xss"
      using ‹xs ∈ set xss› ‹distincts (ys # xss)›
      by (simp add: distincts_Cons Cons.hyps)
    finally
    show "lists_succ (xs # remove1 xs (ys # xss)) = lists_succ (ys # xss)"
      using Cons dists by (auto simp: lists_succ_Cons_pf distincts_Cons)
  qed
qed

lemma lists_succ_no_order:
  assumes "distincts xss" "distincts yss" "set xss = set yss"
  shows "lists_succ xss = lists_succ yss"
  using assms
proof (induct xss arbitrary: yss)
  case Nil then show ?case by simp
next
  case (Cons xs xss)
  have "xs ∉ set xss" "xs ∈ set yss" using Cons.prems
    by (auto dest: distincts_distinct)
  have "lists_succ xss = lists_succ (remove1 xs yss)"
    using Cons.prems ‹xs ∉ _›
    by (intro Cons.hyps) (auto simp add: distincts_Cons distincts_remove1 distincts_distinct)
  then have "lists_succ (xs # xss) = lists_succ (xs # remove1 xs yss)"
    using Cons.prems ‹xs ∈ _›
    by (simp add: lists_succ_Cons_pf distincts_Cons_remove1)
  then show ?case
    using Cons.prems ‹xs ∈ _› by (simp add: lists_succ_remove1)
qed



section ‹List Orbits›

text ‹Computes the orbit of @{term x} under @{term f}›
definition orbit_list :: "('a ⇒ 'a) ⇒ 'a ⇒ 'a list" where
  "orbit_list f x ≡ iterate 0 (funpow_dist1 f x x) f x"

partial_function (tailrec)
  orbit_list_impl :: "('a ⇒ 'a) ⇒ 'a ⇒ 'a list ⇒ 'a ⇒ 'a list"
where
  "orbit_list_impl f s acc x = (let x' = f x in if x' = s then rev (x # acc) else orbit_list_impl f s (x # acc) x')"

context notes [simp] = length_minus_list_mset begin
text ‹Computes the list of orbits›
fun orbits_list :: "('a ⇒ 'a) ⇒ 'a list ⇒ 'a list list" where
  "orbits_list f [] = []"
| "orbits_list f (x # xs) =
     orbit_list f x # orbits_list f (minus_list_mset xs (orbit_list f x))"

fun orbits_list_impl :: "('a ⇒ 'a) ⇒ 'a list ⇒ 'a list list" where
  "orbits_list_impl f [] = []"
| "orbits_list_impl f (x # xs) =
     (let fc = orbit_list_impl f x [] x in fc # orbits_list_impl f (minus_list_mset xs fc))"

declare orbit_list_impl.simps[code]
end

abbreviation sset :: "'a list list ⇒ 'a set set" where
  "sset xss ≡ set ` set xss"

lemma iterate_funpow_step:
  assumes "f x ≠ y" "y ∈ orbit f x"
  shows "iterate 0 (funpow_dist1 f x y) f x = x # iterate 0 (funpow_dist1 f (f x) y) f (f x)"
proof -
  from assms have A: "y ∈ orbit f (f x)" by (simp add: orbit_step)
  have "iterate 0 (funpow_dist1 f x y) f x = x # iterate 1 (funpow_dist1 f x y) f x" (is "_ = _ # ?it")
    unfolding iterate_def by (simp add: upt_rec) 
  also have "?it = map (λn. (f ^^ n) x) (map Suc [0..<funpow_dist f (f x) y])"
    unfolding iterate_def map_Suc_upt by simp
  also have "… = map (λn. (f ^^ n) (f x)) [0..<funpow_dist f (f x) y]"
    by (simp add: funpow_swap1)
  also have "… = iterate 0 (funpow_dist1 f (f x) y) f (f x)"
    unfolding iterate_def
    unfolding iterate_def by (simp add: funpow_dist_step[OF assms(1) A])
  finally show ?thesis .
qed

lemma orbit_list_impl_conv:
  assumes "y ∈ orbit f x"
  shows "orbit_list_impl f y acc x = rev acc @ iterate 0 (funpow_dist1 f x y) f x"
  using assms
proof (induct n≡"funpow_dist1 f x y" arbitrary: x acc)
  case (Suc x)

  show ?case
  proof cases
    assume "f x = y"
    then show ?thesis by (subst orbit_list_impl.simps) (simp add: Let_def iterate_def funpow_dist_0)
  next
    assume not_y :"f x ≠ y"

    have y_in_succ: "y ∈ orbit f (f x)"
      by (intro orbit_step Suc.prems not_y)

    have "orbit_list_impl f y acc x = orbit_list_impl f y (x # acc) (f x)"
      using not_y by (subst orbit_list_impl.simps) simp
    also have "… = rev (x # acc) @ iterate 0 (funpow_dist1 f (f x) y) f (f x)" (is "_ = ?rev @ ?it")
      by (intro Suc funpow_dist_step not_y y_in_succ)
    also have "… = rev acc @ iterate 0 (funpow_dist1 f x y) f x"
      using not_y Suc.prems by (simp add: iterate_funpow_step)
    finally show ?thesis .
  qed
qed

lemma orbit_list_conv_impl:
  assumes "x ∈ orbit f x"
  shows "orbit_list f x = orbit_list_impl f x [] x"
  unfolding orbit_list_impl_conv[OF assms] orbit_list_def by simp


lemma set_orbit_list:
  assumes "x ∈ orbit f x"
  shows "set (orbit_list f x) = orbit f x"
  by (simp add: orbit_list_def orbit_conv_funpow_dist1[OF assms] set_iterate)

lemma set_orbit_list':
  assumes "permutation f" shows "set (orbit_list f x) = orbit f x"
  using assms by (simp add: permutation_self_in_orbit set_orbit_list)

lemma distinct_orbit_list:
  assumes "x ∈ orbit f x"
  shows "distinct (orbit_list f x)"
  by (simp del: upt_Suc add: orbit_list_def iterate_def distinct_map inj_on_funpow_dist1[OF assms])

lemma distinct_orbit_list':
  assumes "permutation f" shows "distinct (orbit_list f x)"
  using assms by (simp add: permutation_self_in_orbit distinct_orbit_list)

lemma orbits_list_conv_impl:
  assumes "permutation f"
  shows "orbits_list f xs = orbits_list_impl f xs"
proof (induct "length xs" arbitrary: xs rule: less_induct)
  case less show ?case
    using assms by (cases xs) (auto simp: assms less less_Suc_eq_le length_minus_list_mset
      orbit_list_conv_impl permutation_self_in_orbit Let_def)
qed

lemma orbit_list_not_nil[simp]: "orbit_list f x ≠ []"
  by (simp add: orbit_list_def)

lemma sset_orbits_list:
  assumes "permutation f" shows "sset (orbits_list f xs) = (orbit f) ` set xs"
proof (induct "length xs" arbitrary: xs rule: less_induct)
  case less
  show ?case
  proof (cases xs)
    case Nil then show ?thesis by simp
  next
    case (Cons x' xs')
    let ?xs'' = "minus_list_mset xs' (orbit_list f x')"
    have A: "sset (orbits_list f ?xs'') = orbit f ` set ?xs''"
      using Cons by (simp add: less_Suc_eq_le length_minus_list_mset less.hyps)
    have B: "set (orbit_list f x') = orbit f x'"
      by (rule set_orbit_list) (simp add: permutation_self_in_orbit assms)

    have "orbit f ` set (minus_list_mset xs' (orbit_list f x')) ⊆ orbit f ` set xs'"
      using minus_list_mset_subset[of xs'] by auto
    moreover
    have "orbit f ` set xs' - {orbit f x'} ⊆ (orbit f ` set (minus_list_mset xs' (orbit_list f x')))" (is "?L ⊆ ?R")
    proof
      fix A assume "A ∈ ?L"
      then obtain y where "A = orbit f y" "y ∈ set xs'" by auto
      have "A ≠ orbit f x'" using ‹A ∈ ?L› by auto
      from ‹A = _› ‹A ≠ _› have "y ∉ orbit f x'"
        by (meson assms cyclic_on_orbit orbit_cyclic_eq3 permutation_permutes)
      with ‹y ∈ _› have "y ∈ set (minus_list_mset xs' (orbit_list f x'))"
        using minus_list_set_subset_minus_list_mset
        by (fastforce simp: set_orbit_list permutation_self_in_orbit assms)
      then show "A ∈ ?R" using ‹A = _› by auto
    qed
    ultimately
    show ?thesis by (auto simp: A B Cons)
  qed
qed



subsection ‹Relation to @{term cyclic_on}›

lemma list_succ_orbit_list:
  assumes "s ∈ orbit f s" "⋀x. x ∉ orbit f s ⟹ f x = x"
  shows "list_succ (orbit_list f s) = f"
proof -
  have "distinct (orbit_list f s)" "⋀x. x ∉ set (orbit_list f s) ⟹ x = f x"
    using assms by (simp_all add: distinct_orbit_list set_orbit_list)
  moreover
  have "⋀i. i < length (orbit_list f s) ⟹ orbit_list f s ! (Suc i mod length (orbit_list f s)) = f (orbit_list f s ! i)"
    using funpow_dist1_prop[OF ‹s ∈ orbit f s›] by (auto simp: orbit_list_def funpow_mod_eq)
  ultimately show ?thesis
    by (auto simp: list_succ_def fun_eq_iff)
qed

lemma list_succ_funpow_conv:
  assumes A: "distinct xs" "x ∈ set xs"
  shows "(list_succ xs ^^ n) x = xs ! ((index xs x + n) mod length xs)"
proof -
  have "xs ≠ []" using assms by auto
  then show ?thesis
    by (induct n) (auto simp: hd_conv_nth A index_nth_id list_succ_def mod_simps)
qed

lemma orbit_list_succ:
  assumes "distinct xs" "x ∈ set xs"
  shows "orbit (list_succ xs) x = set xs"
proof (intro set_eqI iffI)
  fix y assume "y ∈ orbit (list_succ xs) x"
  then show "y ∈ set xs"
    by induct (auto simp: list_succ_in_conv ‹x ∈ set xs›)
next
  fix y assume "y ∈ set xs"
  moreover
  { fix i j have "i < length xs ⟹ j < length xs ⟹ ∃n. xs ! j = xs ! ((i + n) mod length xs)"
      using assms by (auto simp: exI[where x="j + (length xs - i)"])
  }
  ultimately
  show "y ∈ orbit (list_succ xs) x"
    using assms by (auto simp: orbit_altdef_permutation permutation_list_succ list_succ_funpow_conv index_nth_id in_set_conv_nth)
qed

lemma cyclic_on_list_succ:
  assumes "distinct xs" "xs ≠ []" shows "cyclic_on (list_succ xs) (set xs)"
  using assms last_in_set by (auto simp: cyclic_on_def orbit_list_succ)

lemma obtain_orbit_list_func:
  assumes "s ∈ orbit f s" "⋀x. x ∉ orbit f s ⟹ f x = x"
  obtains xs where "f = list_succ xs" "set xs = orbit f s" "distinct xs" "hd xs = s"
proof -
  { from assms have "f = list_succ (orbit_list f s)" by (simp add: list_succ_orbit_list)
    moreover
    have "set (orbit_list f s) = orbit f s" "distinct (orbit_list f s)"
      by (auto simp: set_orbit_list distinct_orbit_list assms)
    moreover have "hd (orbit_list f s) = s"
      by (simp add: orbit_list_def iterate_def hd_map del: upt_Suc)
    ultimately have "∃xs. f = list_succ xs ∧ set xs = orbit f s ∧ distinct xs ∧ hd xs = s" by blast
  } then show ?thesis by (metis that)
qed

lemma cyclic_on_obtain_list_succ:
  assumes "cyclic_on f S" "⋀x. x ∉ S ⟹ f x = x"
  obtains xs where "f = list_succ xs" "set xs = S" "distinct xs"
proof -
  from assms obtain s where s: "s ∈ orbit f s" "⋀x. x ∉ orbit f s ⟹ f x = x"  "S = orbit f s"
    by (auto simp: cyclic_on_def)
  then show ?thesis by (metis that obtain_orbit_list_func)
qed

lemma cyclic_on_obtain_list_succ':
  assumes "cyclic_on f S" "f permutes S"
  obtains xs where "f = list_succ xs" "set xs = S" "distinct xs"
  using assms unfolding permutes_def by (metis cyclic_on_obtain_list_succ)

lemma list_succ_unique:
  assumes "s ∈ orbit f s" "⋀x. x ∉ orbit f s ⟹ f x = x"
  shows "∃!xs. f = list_succ xs ∧ distinct xs ∧ hd xs = s ∧ set xs = orbit f s"
proof -
  from assms obtain xs where xs: "f = list_succ xs" "distinct xs" "hd xs = s" "set xs = orbit f s" 
    by (rule obtain_orbit_list_func)
  moreover
  { fix zs
    assume A: "f = list_succ zs" "distinct zs" "hd zs = s" "set zs = orbit f s"
    then have "zs ≠ []" using ‹s ∈ orbit f s› by auto
    from ‹distinct xs› ‹distinct zs› ‹set xs = orbit f s› ‹set zs = orbit f s›
    have len: "length xs = length zs" by (metis distinct_card)

    { fix n assume "n < length xs"
      then have "zs ! n = xs ! n"
      proof (induct n)
        case 0 with A xs ‹zs ≠ []› show ?case by (simp add: hd_conv_nth nth_rotate_conv_nth)
      next
        case (Suc n)
        then have "list_succ zs (zs ! n) = list_succ xs (xs! n)"
          using ‹f = list_succ xs› ‹f = list_succ zs› by simp
        with ‹Suc n < _› show ?case
          by (simp add:list_succ_nth len ‹distinct xs› ‹distinct zs›)
      qed }
    then have "zs = xs" by (metis len nth_equalityI) }
  ultimately show ?thesis by metis
qed

lemma distincts_orbits_list:
  assumes "distinct as" "permutation f"
  shows "distincts (orbits_list f as)"
  using assms(1)
proof (induct "length as" arbitrary: as rule: less_induct)
  case less
  show ?case
  proof (cases as)
    case Nil then show ?thesis by simp
  next
    case (Cons a as')
    let ?as' = "fold remove1 (orbit_list f a) as'"
    from Cons less.prems have A: "distincts (orbits_list f (minus_list_mset as' (orbit_list f a)))"
      by (intro less) (auto simp: distinct_minus_list_mset length_minus_list_mset less_Suc_eq_le)

    have B: "set (orbit_list f a) ∩ ⋃(sset (orbits_list f (minus_list_mset as' (orbit_list f a)))) = {}"
    proof -
      have "orbit f a ∩ set (minus_list_mset as' (orbit_list f a)) = {}"
        using assms less.prems Cons by (simp add: set_minus_list_mset_distinct set_orbit_list')
      then have "orbit f a ∩ ⋃ (orbit f ` set (minus_list_mset as' (orbit_list f a))) = {}"
        by auto (metis assms(2) cyclic_on_orbit disjoint_iff_not_equal permutation_self_in_orbit[OF assms(2)] orbit_cyclic_eq3 permutation_permutes)
      then show ?thesis using assms
      by (auto simp: set_orbit_list' sset_orbits_list disjoint_iff_not_equal)
    qed
    show ?thesis
      using A B assms by (auto simp: distincts_Cons Cons distinct_orbit_list')
  qed
qed

lemma cyclic_on_lists_succ':
  assumes "distincts xss"
  shows "A ∈ sset xss ⟹ cyclic_on (lists_succ xss) A"
  using assms
proof (induction xss arbitrary: A)
  case Nil then show ?case by auto
next
  case (Cons xs xss A)
  then have inter: "set xs ∩ (⋃ys∈set xss. set ys) = {}" by (auto simp: distincts_Cons)

  note pcp[OF _ _ inter] = permutes_comp_preserves_cyclic1 permutes_comp_preserves_cyclic2
  from Cons show "cyclic_on (lists_succ (xs # xss)) A"
    by (cases "A = set xs")
      (auto intro: pcp simp: cyclic_on_list_succ list_succ_permutes
        lists_succ_permutes lists_succ_Cons_pf distincts_Cons)
qed

lemma cyclic_on_lists_succ:
  assumes "distincts xss"
  shows "⋀xs. xs ∈ set xss ⟹ cyclic_on (lists_succ xss) (set xs)"
  using assms by (auto intro: cyclic_on_lists_succ')

lemma permutes_as_lists_succ:
  assumes "distincts xss"
  assumes ls_eq: "⋀xs. xs ∈ set xss ⟹ list_succ xs = perm_restrict f (set xs)"
  assumes "f permutes (⋃(sset xss))"
  shows "f = lists_succ xss"
  using assms
proof (induct xss arbitrary: f)
  case Nil then show ?case by simp
next
  case (Cons xs xss)
  let ?sets = "λxss. ⋃ys ∈ set xss. set ys"

  have xs: "distinct xs" "xs ≠ []" using Cons by (auto simp: distincts_Cons)

  have f_xs: "perm_restrict f (set xs) = list_succ xs"
    using Cons by simp

  have co_xs: "cyclic_on (perm_restrict f (set xs)) (set xs)"
    unfolding f_xs using xs by (rule cyclic_on_list_succ)

  have perm_xs: "perm_restrict f (set xs) permutes set xs"
    unfolding f_xs using ‹distinct xs› by (rule list_succ_permutes)

  have perm_xss: "perm_restrict f (?sets xss) permutes (?sets xss)"
  proof -
    have "perm_restrict f (?sets (xs # xss) - set xs) permutes (?sets (xs # xss) - set xs)"
      using Cons co_xs by (intro perm_restrict_diff_cyclic) (auto simp: cyclic_on_perm_restrict)
    also have "?sets (xs # xss) - set xs = ?sets xss"
      using Cons by (auto simp: distincts_Cons)
    finally show ?thesis .
  qed

  have f_xss: "perm_restrict f (?sets xss) = lists_succ xss"
  proof -
    have *: "⋀xs. xs ∈ set xss ⟹ ((⋃x∈set xss. set x) ∩ set xs) = set xs"
      by blast
    with perm_xss Cons.prems show ?thesis
      by (intro Cons.hyps) (auto simp: distincts_Cons perm_restrict_perm_restrict *)
  qed

  from Cons.prems show "f = lists_succ (xs # xss)"
    by (simp add: lists_succ_Cons_pf distincts_Cons f_xss[symmetric]
      perm_restrict_union perm_xs perm_xss)
qed

lemma cyclic_on_obtain_lists_succ:
  assumes
    permutes: "f permutes S" and
    S: "S = ⋃(sset css)" and
    dists: "distincts css" and
    cyclic: "⋀cs. cs ∈ set css ⟹ cyclic_on f (set cs)"
  obtains xss where "f = lists_succ xss" "distincts xss" "map set xss = map set css" "map hd xss = map hd css"
proof -
  let ?fc = "λcs. perm_restrict f (set cs)"
  define some_list where "some_list cs = (SOME xs. ?fc cs = list_succ xs ∧ set xs = set cs ∧ distinct xs ∧ hd xs = hd cs)" for cs
  { fix cs assume "cs ∈ set css"
    then have "cyclic_on (?fc cs) (set cs)" "⋀x. x ∉ set cs ⟹ ?fc cs x = x" "hd cs ∈ set cs"
      using cyclic dists by (auto simp add: cyclic_on_perm_restrict perm_restrict_def distincts_def)
    then have "hd cs ∈ orbit (?fc cs) (hd cs)"  "⋀x. x ∉ orbit (?fc cs) (hd cs) ⟹ ?fc cs x = x" "hd cs ∈ set cs" "set cs = orbit (?fc cs) (hd cs)"
      by (auto simp: cyclic_on_alldef)
    then have "∃xs. ?fc cs = list_succ xs ∧ set xs = set cs ∧ distinct xs ∧ hd xs = hd cs"
      by (metis obtain_orbit_list_func)
    then have "?fc cs = list_succ (some_list cs) ∧ set (some_list cs) = set cs ∧ distinct (some_list cs) ∧ hd (some_list cs) = hd cs"
      unfolding some_list_def by (rule someI_ex)
    then have "?fc cs = list_succ (some_list cs)" "set (some_list cs) = set cs" "distinct (some_list cs)" "hd (some_list cs) = hd cs"
      by auto
  } note sl_cs  = this

  have "⋀cs. cs ∈ set css ⟹ cs ≠ []" using dists by (auto simp: distincts_def)
  then have some_list_ne: "⋀cs. cs ∈ set css ⟹ some_list cs ≠ []"
    by (metis set_empty sl_cs(2))

  have set: "map set (map some_list css) = map set css" "map hd (map some_list css) = map hd css"
    using sl_cs(2,4) by (auto simp add: map_idI)

  have distincts: "distincts (map some_list css)"
  proof -
    have c_dist: "⋀xs ys. ⟦xs∈set css; ys∈set css; xs ≠ ys⟧ ⟹ set xs ∩ set ys = {}"
      using dists by (auto simp: distincts_def)

    have "distinct (map some_list css)"
    proof -
      have "inj_on some_list (set css)"
        using sl_cs(2) c_dist by (intro inj_onI) (metis inf.idem set_empty) 
      with ‹distincts css› show ?thesis
        by (auto simp: distincts_distinct distinct_map)
    qed
    moreover
    have "∀xs∈set (map some_list css). distinct xs ∧ xs ≠ []"
      using sl_cs(3) some_list_ne by auto
    moreover
    from c_dist have "(∀xs∈set (map some_list css). ∀ys∈set (map some_list css). xs ≠ ys ⟶ set xs ∩ set ys = {})"
      using sl_cs(2) by auto
    ultimately
    show ?thesis by (simp add: distincts_def)
  qed

  have f: "f = lists_succ (map some_list css)"
    using distincts
  proof (rule permutes_as_lists_succ)
    fix xs assume "xs ∈ set (map some_list css)"
    then show "list_succ xs = perm_restrict f (set xs)"
      using sl_cs(1) sl_cs(2) by auto
  next
    have "S = (⋃xs∈set (map some_list css). set xs)"
      using S sl_cs(2) by auto
    with permutes show "f permutes ⋃(sset (map some_list css))"
      by simp
  qed

  from f distincts set  show ?thesis ..
qed


subsection ‹Permutations of a List›

lemma length_remove1_less:
  assumes "x ∈ set xs" shows "length (remove1 x xs) < length xs"
proof -
  from assms have "0 < length xs" by auto
  with assms show ?thesis by (auto simp: length_remove1)
qed
context notes [simp] = length_remove1_less begin
fun permutations :: "'a list ⇒ 'a list list" where
  permutations_Nil: "permutations [] = [[]]"
| permutations_Cons:
    "permutations xs = [y # ys. y <- xs, ys <- permutations (remove1 y xs)]"
end

declare permutations_Cons[simp del]

text ‹
  The function above returns all permutations of a list. The function below computes
  only those which yield distinct cyclic permutation functions (cf. @{term list_succ}).
›

fun cyc_permutations :: "'a list ⇒ 'a list list" where
  "cyc_permutations [] = [[]]"
| "cyc_permutations (x # xs) = map (Cons x) (permutations xs)"



lemma nil_in_permutations[simp]: "[] ∈ set (permutations xs) ⟷ xs = []"
  by (induct xs) (auto simp: permutations_Cons)

lemma permutations_not_nil:
  assumes "xs ≠ []"
  shows "permutations xs = concat (map (λx. map ((#) x) (permutations (remove1 x xs))) xs)"
  using assms by (cases xs) (auto simp: permutations_Cons)

lemma set_permutations_step:
  assumes "xs ≠ []"
  shows "set (permutations xs) = (⋃x ∈ set xs. Cons x ` set (permutations (remove1 x xs)))"
  using assms by (cases xs) (auto simp: permutations_Cons)

lemma in_set_permutations:
  assumes "distinct xs"
  shows "ys ∈ set (permutations xs) ⟷ distinct ys ∧ set xs = set ys" (is "?L xs ys ⟷ ?R xs ys")
  using assms
proof (induct "length xs" arbitrary: xs ys)
  case 0 then show ?case by auto
next
  case (Suc n)
  then have "xs ≠ []" by auto

  show ?case
  proof
    assume "?L xs ys"
    then obtain y ys' where "ys = y # ys'" "y ∈ set xs" "ys' ∈ set (permutations (remove1 (hd ys) xs))"
      using ‹xs ≠ []› by (auto simp: permutations_not_nil)
    moreover
    then have "?R (remove1 y xs) ys'"
      using Suc.prems Suc.hyps(2) by (intro Suc.hyps(1)[THEN iffD1]) (auto simp: length_remove1)
    ultimately show "?R xs ys"
      using Suc by auto
  next
    assume "?R xs ys"
    with ‹xs ≠ []› obtain y ys' where "ys = y # ys'" "y ∈ set xs" by (cases ys) auto
    moreover
    then have "ys' ∈ set (permutations (remove1 y xs))"
      using Suc ‹?R xs ys› by (intro Suc.hyps(1)[THEN iffD2]) (auto simp: length_remove1)
    ultimately
    show "?L xs ys"
      using ‹xs ≠ []› by (auto simp: permutations_not_nil)
  qed
qed

lemma in_set_cyc_permutations:
  assumes "distinct xs"
  shows "ys ∈ set (cyc_permutations xs) ⟷ distinct ys ∧ set xs = set ys ∧ hd ys = hd xs" (is "?L xs ys ⟷ ?R xs ys")
proof (cases xs)
  case (Cons x xs) with assms show ?thesis
    by (cases ys) (auto simp: in_set_permutations intro!: imageI)
qed auto

lemma in_set_cyc_permutations_obtain:
  assumes "distinct xs" "distinct ys" "set xs = set ys"
  obtains n where "rotate n ys ∈ set (cyc_permutations xs)"
proof (cases xs)
  case Nil with assms have "rotate 0 ys ∈ set (cyc_permutations xs)" by auto
  then show ?thesis ..
next
  case (Cons x xs')
  let ?ys' = "rotate (index ys x) ys"
  have "ys ≠ []" "x ∈ set ys"
    using Cons assms by auto
  then have "distinct ?ys' ∧ set xs = set ?ys' ∧ hd ?ys' = hd xs"
    using assms Cons by (auto simp add: hd_rotate_conv_nth)
  with ‹distinct xs› have "?ys' ∈ set (cyc_permutations xs)"
    by (rule in_set_cyc_permutations[THEN iffD2])
  then show ?thesis ..
qed

lemma list_succ_set_cyc_permutations:
  assumes "distinct xs" "xs ≠ []"
  shows "list_succ ` set (cyc_permutations xs) = {f. f permutes set xs ∧ cyclic_on f (set xs)}" (is "?L = ?R")
proof (intro set_eqI iffI)
  fix f assume "f ∈ ?L"
  moreover have "⋀ys. set xs = set ys ⟹ xs ≠ [] ⟹ ys ≠ []" by auto
  ultimately show "f ∈ ?R"
    using assms by (auto simp: in_set_cyc_permutations list_succ_permutes cyclic_on_list_succ)
next
  fix f assume "f ∈ ?R"
  then obtain ys where ys: "list_succ ys = f" "distinct ys" "set ys = set xs"
    by (auto elim: cyclic_on_obtain_list_succ')
  moreover
  with ‹distinct xs› obtain n where "rotate n ys ∈ set (cyc_permutations xs)"
    by (auto elim: in_set_cyc_permutations_obtain)
  then have "list_succ (rotate n ys) ∈ ?L" by simp
  ultimately
  show "f ∈ ?L" by simp
qed


subsection ‹Enumerating Permutations from List Orbits›

definition cyc_permutationss :: "'a list list ⇒ 'a list list list" where
  "cyc_permutationss = product_lists o map cyc_permutations"

lemma cyc_permutationss_Nil[simp]: "cyc_permutationss [] = [[]]"
  by (auto simp: cyc_permutationss_def)

lemma in_set_cyc_permutationss:
  assumes "distincts xss"
  shows "yss ∈ set (cyc_permutationss xss) ⟷ distincts yss ∧ map set xss = map set yss ∧ map hd xss = map hd yss"
proof -
  { assume A: "list_all2 (λx ys. x ∈ set ys) yss (map cyc_permutations xss)"
    then have "length yss = length xss" by (auto simp: list_all2_lengthD)
    then have "⋃(sset xss) = ⋃(sset yss)" "distincts yss" "map set xss = map set yss" "map hd xss = map hd yss"
      using A assms
      by (induct yss xss rule: list_induct2) (auto simp: distincts_Cons in_set_cyc_permutations)
  } note X = this
  { assume A: "distincts yss" "map set xss = map set yss" "map hd xss = map hd yss"
    then have "length yss = length xss" by (auto dest: map_eq_imp_length_eq)
    then have "list_all2 (λx ys. x ∈ set ys) yss (map cyc_permutations xss)"
      using A assms
      by (induct yss xss rule: list_induct2) (auto simp: distincts_Cons in_set_cyc_permutations)
  } note Y = this
  show "?thesis"
    unfolding cyc_permutationss_def
    by (auto simp: product_lists_set intro: X Y)
qed

lemma lists_succ_set_cyc_permutationss:
  assumes "distincts xss"
  shows "lists_succ ` set (cyc_permutationss xss) = {f. f permutes ⋃(sset xss) ∧ (∀c ∈ sset xss. cyclic_on f c)}" (is "?L = ?R")
  using assms
proof (intro set_eqI iffI)
  fix f assume "f ∈ ?L"
  then obtain yss where "yss ∈ set (cyc_permutationss xss)" "f = lists_succ yss" by (rule imageE)
  moreover
  from ‹yss ∈ _› assms have "set (map set xss) = set (map set yss)"
    by (auto simp: in_set_cyc_permutationss)
  then have "sset xss = sset yss" by simp
  ultimately
  show "f ∈ ?R"
    using assms
  by (auto simp: in_set_cyc_permutationss cyclic_on_lists_succ') (metis lists_succ_permutes)
next
  fix f assume "f ∈ ?R"
  then have "f permutes ⋃(sset xss)" "⋀cs. cs ∈ set xss ⟹ cyclic_on f (set cs)"
    by auto
  from this(1) refl assms this(2)
  obtain yss where "f = lists_succ yss" "distincts yss" "map set yss = map set xss" "map hd yss = map hd xss"
    by (rule cyclic_on_obtain_lists_succ)
  with assms show "f ∈ ?L" by (auto intro!: imageI simp: in_set_cyc_permutationss)
qed


subsection ‹Lists of Permutations›

definition permutationss :: "'a list list ⇒ 'a list list list" where
  "permutationss = product_lists o map permutations"

lemma permutationss_Nil[simp]: "permutationss [] = [[]]"
  by (auto simp: permutationss_def)

lemma permutationss_Cons:
  "permutationss (xs # xss) = concat (map (λys. map (Cons ys) (permutationss xss)) (permutations xs))"
  by (auto simp: permutationss_def)

lemma in_set_permutationss:
  assumes "distincts xss"
  shows "yss ∈ set (permutationss xss) ⟷ distincts yss ∧ map set xss = map set yss"
proof -
  { assume A: "list_all2 (λx ys. x ∈ set ys) yss (map permutations xss)"
    then have "length yss = length xss" by (auto simp: list_all2_lengthD)
    then have "⋃(sset xss) = ⋃(sset yss)" "distincts yss" "map set xss = map set yss"
      using A assms
      by (induct yss xss rule: list_induct2) (auto simp: distincts_Cons in_set_permutations)
  } note X = this
  { assume A: "distincts yss" "map set xss = map set yss"
    then have "length yss = length xss" by (auto dest: map_eq_imp_length_eq)
    then have "list_all2 (λx ys. x ∈ set ys) yss (map permutations xss)"
      using A assms
      by (induct yss xss rule: list_induct2) (auto simp: in_set_permutations distincts_Cons)
  } note Y = this
  show "?thesis"
    unfolding permutationss_def
    by (auto simp: product_lists_set intro: X Y)
qed

lemma set_permutationss:
  assumes "distincts xss"
  shows "set (permutationss xss) = {yss. distincts yss ∧ map set xss = map set yss}"
  using in_set_permutationss[OF assms] by blast

lemma permutationss_complete:
  assumes "distincts xss" "distincts yss" "xss ≠ []"
    and "set ` set xss = set ` set yss"
  shows "set yss ∈ set ` set (permutationss xss)"
proof -
  have "length xss = length yss"
    using assms by (simp add: length_distincts)
  from ‹sset xss = _›
  have "∃yss'. set yss' = set yss ∧ map set yss' = map set xss"
    using assms(1-2)
  proof (induct xss arbitrary: yss)
    case Nil then show ?case by simp
  next
    case (Cons xs xss)
    from ‹sset (xs # xss) = sset yss›
    obtain ys where ys: "ys ∈ set yss" "set ys = set xs"
      by auto (metis imageE insertI1)
    with ‹distincts yss› have "set ys ∉ sset (remove1 ys yss)"
      by (fastforce simp: distincts_def)
    moreover
    from ‹distincts (xs # xss)› have "set xs ∉ sset xss"
      by (fastforce simp: distincts_def)
    ultimately have "sset xss = sset (remove1 ys yss)"
      using ‹distincts yss› ‹sset (xs # xss) = sset yss›
      apply (auto simp: distincts_distinct ‹set ys = set xs›[symmetric])
      apply (smt Diff_insert_absorb ‹ys ∈ set yss› image_insert insert_Diff rev_image_eqI)
      by (metis ‹ys ∈ set yss› image_eqI insert_Diff insert_iff)
    then obtain yss' where "set yss' = set (remove1 ys yss) ∧ map set yss' = map set xss"
      using Cons by atomize_elim (auto simp: distincts_Cons distincts_remove1)
    then have "set (ys # yss') = set yss ∧ map set (ys # yss') = map set (xs # xss)"
      using ys set_remove1_eq ‹distincts yss› by (auto simp: distincts_distinct)
    then show ?case ..
  qed
  then obtain yss' where "set yss' = set yss" "map set yss' = map set xss" by blast
  then have "distincts yss'" using ‹distincts xss› ‹distincts yss›
    unfolding distincts_def
    by simp_all (metis ‹length xss = length yss› card_distinct distinct_card length_map)
  then have "set yss' ∈ set ` set (permutationss xss)"
    using ‹distincts xss› ‹map set yss' = _›
    by (auto simp: set_permutationss)
  then show ?thesis using ‹set yss' = _› by auto
qed

lemma permutations_complete: (* could generalize with multi-sets *)
  assumes "distinct xs" "distinct ys" "set xs = set ys"
  shows "ys ∈ set (permutations xs)"
  using assms
proof (induct "length xs" arbitrary: xs ys)
  case 0 then show ?case by simp
next
  case (Suc n)
  from Suc.hyps have "xs ≠ []" by auto
  then obtain y ys' where [simp]: "ys = y # ys'" "y ∈ set xs" using Suc.prems by (cases ys) auto
  have "ys' ∈ set (permutations (remove1 y xs))"
    using Suc.prems ‹Suc n = _› by (intro Suc.hyps) (simp_all add: length_remove1 )
  then show ?case using ‹xs ≠ []› by (auto simp: set_permutations_step)
qed


end