Theory Perfect_Fields
section ‹Perfect Fields›
theory Perfect_Fields
imports
  "HOL-Computational_Algebra.Computational_Algebra"
  "Berlekamp_Zassenhaus.Finite_Field" 
begin
lemma (in vector_space) bij_betw_representation:
  assumes [simp]: "independent B" "finite B"
  shows   "bij_betw (λv. ∑b∈B. scale (v b) b) (B →⇩E UNIV) (span B)"
proof (rule bij_betwI)
  show "(λv. ∑b∈B. v b *s b) ∈ (B →⇩E UNIV) → local.span B"
    (is "?f ∈ _")
    by (auto intro: span_sum span_scale span_base)
  show "(λx. restrict (representation B x) B) ∈ local.span B → B →⇩E UNIV"
    (is "?g ∈ _") by auto
  show "?g (?f v) = v" if "v ∈ B →⇩E UNIV" for v
  proof
    fix b :: 'b
    show "?g (?f v) b = v b"
    proof (cases "b ∈ B")
      case b: True
      have "?g (?f v) b = (∑i∈B. local.representation B (v i *s i) b)"
        using b by (subst representation_sum) (auto intro: span_scale span_base)
      also have "… = (∑i∈B. v i * local.representation B i b)"
        by (intro sum.cong) (auto simp: representation_scale span_base)
      also have "… = (∑i∈{b}. v i * local.representation B i b)"
        by (intro sum.mono_neutral_right) (auto simp: representation_basis b)
      also have "… = v b"
        by (simp add: representation_basis b)
      finally show "?g (?f v) b = v b" .
    qed (use that in auto)
  qed
  show "?f (?g v) = v" if "v ∈ span B" for v
    using that by (simp add: sum_representation_eq)
qed
lemma (in vector_space) card_span:
  assumes [simp]: "independent B" "finite B"
  shows   "card (span B) = CARD('a) ^ card B"
proof -
  have "card (B →⇩E (UNIV :: 'a set)) = card (span B)"
    by (rule bij_betw_same_card, rule bij_betw_representation) fact+
  thus ?thesis
    by (simp add: card_PiE dim_span_eq_card_independent)
qed
lemma (in zero_neq_one) CARD_neq_1: "CARD('a) ≠ Suc 0"
proof
  assume "CARD('a) = Suc 0"
  have "{0, 1} ⊆ (UNIV :: 'a set)"
    by simp
  also have "is_singleton (UNIV :: 'a set)"
    by (simp add: is_singleton_altdef ‹CARD('a) = _›)
  then obtain x :: 'a where "UNIV = {x}"
    by (elim is_singletonE)
  finally have "0 = (1 :: 'a)"
    by blast
  thus False
    using zero_neq_one by contradiction
qed
theorem CARD_finite_field_is_CHAR_power: "∃n>0. CARD('a :: finite_field) = CHAR('a) ^ n"
proof -
  define s :: "'a ring_char mod_ring ⇒ 'a ⇒ 'a" where
    "s = (λx y. of_int (to_int_mod_ring x) * y)"
  interpret vector_space s
    by unfold_locales (auto simp: s_def algebra_simps to_int_mod_ring_add to_int_mod_ring_mult)
  obtain B where B: "independent B" "span B = UNIV"
    by (rule basis_exists[of UNIV]) auto
  have [simp]: "finite B"
    by simp
  have "card (span B) = CHAR('a) ^ card B"
    using B by (subst card_span) auto
  hence *: "CARD('a) = CHAR('a) ^ card B"
    using B by simp
  from * have "card B ≠ 0"
    by (auto simp: B(2) CARD_neq_1)
  with * show ?thesis
    by blast
qed
subsection ‹The Freshman's Dream in rings of non-zero characteristic›
lemma (in comm_semiring_1) freshmans_dream:
  fixes x y :: 'a and n :: nat
  assumes "prime CHAR('a)"
  assumes n_def: "n = CHAR('a)"
  shows   "(x + y) ^ n = x ^ n + y ^ n"
proof -
  interpret comm_semiring_prime_char
    by standard (auto intro!: exI[of _ "CHAR('a)"] assms)
  have "n > 0"
    unfolding n_def by simp
  have "(x + y) ^ n = (∑k≤n. of_nat (n choose k) * x ^ k * y ^ (n - k))"
    by (rule binomial_ring)
  also have "… = (∑k∈{0,n}. of_nat (n choose k) * x ^ k * y ^ (n - k))"
  proof (intro sum.mono_neutral_right ballI)
    fix k assume "k ∈ {..n} - {0, n}"
    hence k: "k > 0" "k < n"
      by auto
    have "CHAR('a) dvd (n choose k)"
      unfolding n_def
      by (rule dvd_choose_prime) (use k in ‹auto simp: n_def›)
    hence "of_nat (n choose k) = (0 :: 'a)"
      using of_nat_eq_0_iff_char_dvd by blast
    thus "of_nat (n choose k) * x ^ k * y ^ (n - k) = 0"
      by simp
  qed auto
  finally show ?thesis
    using ‹n > 0› by (simp add: add_ac)
qed
lemma (in comm_semiring_1) freshmans_dream':
  assumes [simp]: "prime CHAR('a)" and "m = CHAR('a) ^ n"
  shows "(x + y :: 'a) ^ m = x ^ m + y ^ m"
  unfolding assms(2)
proof (induction n)
  case (Suc n)
  have "(x + y) ^ (CHAR('a) ^ n * CHAR('a)) = ((x + y) ^ (CHAR('a) ^ n)) ^ CHAR('a)"
    by (rule power_mult)
  thus ?case
    by (simp add: Suc.IH freshmans_dream Groups.mult_ac flip: power_mult)
qed auto
lemma (in comm_semiring_1) freshmans_dream_sum:
  fixes f :: "'b ⇒ 'a"
  assumes "prime CHAR('a)" and "n = CHAR('a)"
  shows "sum f A ^ n = sum (λi. f i ^ n) A"
  using assms
  by (induct A rule: infinite_finite_induct)
     (auto simp add: power_0_left freshmans_dream)
lemma (in comm_semiring_1) freshmans_dream_sum':
  fixes f :: "'b ⇒ 'a"
  assumes "prime CHAR('a)" "m = CHAR('a) ^ n"
  shows   "sum f A ^ m = sum (λi. f i ^ m) A"
  using assms
  by (induction A rule: infinite_finite_induct)
     (auto simp: freshmans_dream' power_0_left)
subsection ‹The Frobenius endomorphism›
definition (in semiring_1) frob :: "'a ⇒ 'a" where
  "frob x = x ^ CHAR('a)"
definition (in semiring_1) inv_frob :: "'a ⇒ 'a" where
  "inv_frob x = (if x ∈ {0, 1} then x else if x ∈ range frob then inv_into UNIV frob x else x)"
lemma (in semiring_1) inv_frob_0 [simp]: "inv_frob 0 = 0"
  and inv_frob_1 [simp]: "inv_frob 1 = 1"
  by (simp_all add: inv_frob_def)
lemma (in semiring_prime_char) frob_0 [simp]: "frob (0 :: 'a) = 0"
  by (simp add: frob_def power_0_left)
lemma (in semiring_1) frob_1 [simp]: "frob 1 = 1"
  by (simp add: frob_def)
lemma (in comm_semiring_1) frob_mult: "frob (x * y) = frob x * frob (y :: 'a)"
  by (simp add: frob_def power_mult_distrib)
lemma (in comm_semiring_1)
  frob_add: "prime CHAR('a) ⟹ frob (x + y :: 'a) = frob x + frob (y :: 'a)"
  by (simp add: frob_def freshmans_dream)
lemma (in comm_ring_1) frob_uminus: "prime CHAR('a) ⟹ frob (-x :: 'a) = -frob x"
proof -
  assume "prime CHAR('a)"
  hence "frob (-x) + frob x = 0"
    by (subst frob_add [symmetric]) (auto simp: frob_def power_0_left)
  thus ?thesis
    by (simp add: add_eq_0_iff)
qed
lemma (in comm_ring_prime_char) frob_diff:
  "prime CHAR('a) ⟹ frob (x - y :: 'a) = frob x - frob (y :: 'a)"
  using frob_add[of x "-y"] by (simp add: frob_uminus)
interpretation frob_sr: semiring_hom "frob :: 'a :: {comm_semiring_prime_char} ⇒ 'a"
  by standard (auto simp: frob_add frob_mult)
interpretation frob: ring_hom "frob :: 'a :: {comm_ring_prime_char} ⇒ 'a"
  by standard auto
interpretation frob: field_hom "frob :: 'a :: {field_prime_char} ⇒ 'a"
  by standard auto
lemma frob_mod_ring' [simp]: "(x :: 'a :: prime_card mod_ring) ^ CARD('a) = x"
  by (metis CARD_mod_ring finite_field_power_card_eq_same)
lemma frob_mod_ring [simp]: "frob (x :: 'a :: prime_card mod_ring) = x"
  by (simp add: frob_def)
context semiring_1_no_zero_divisors
begin
lemma frob_eq_0D:
  "frob (x :: 'a) = 0 ⟹ x = 0"
  by (auto simp: frob_def)
lemma frob_eq_0_iff [simp]:
  "frob (x :: 'a) = 0 ⟷ x = 0 ∧ CHAR('a) > 0"
  by (auto simp: frob_def)
end
context idom_prime_char
begin
lemma inj_frob: "inj (frob :: 'a ⇒ 'a)"
proof
  fix x y :: 'a
  assume "frob x = frob y"
  hence "frob (x - y) = 0"
    by (simp add: frob_diff del: frob_eq_0_iff)
  thus "x = y"
    by simp
qed
lemma frob_eq_frob_iff [simp]:
  "frob (x :: 'a) = frob y ⟷ x = y"
  using inj_frob by (auto simp: inj_def)
lemma frob_eq_1_iff [simp]: "frob (x :: 'a) = 1 ⟷ x = 1"
  using frob_eq_frob_iff by fastforce
lemma inv_frob_frob [simp]: "inv_frob (frob (x :: 'a)) = x"
  by (simp add: inj_frob inv_frob_def)
lemma frob_inv_frob [simp]:
  assumes "x ∈ range frob"
  shows   "frob (inv_frob x) = (x :: 'a)"
  using assms by (auto simp: inj_frob inv_frob_def)
lemma inv_frob_eqI: "frob y = x ⟹ inv_frob x = y"
  using inv_frob_frob local.frob_def by force
lemma inv_frob_eq_0_iff [simp]: "inv_frob (x :: 'a) = 0 ⟷ x = 0"
  using inj_frob by (auto simp: inv_frob_def split: if_splits)
end
class surj_frob = field_prime_char +
  assumes surj_frob [simp]: "surj (frob :: 'a ⇒ 'a)"
begin
lemma in_range_frob [simp, intro]: "(x :: 'a) ∈ range frob"
  using surj_frob by blast
lemma inv_frob_eq_iff [simp]: "inv_frob (x :: 'a) = y ⟷ frob y = x"
  using frob_inv_frob inv_frob_frob by blast
end
context alg_closed_field
begin
lemma alg_closed_surj_frob:
  assumes "CHAR('a) > 0"
  shows   "surj (frob :: 'a ⇒ 'a)"
proof -
  show "surj (frob :: 'a ⇒ 'a)"
  proof safe
    fix x :: 'a
    obtain y where "y ^ CHAR('a) = x"
      using nth_root_exists CHAR_pos assms by blast
    hence "frob y = x"
      using CHAR_pos by (simp add: frob_def)
    thus "x ∈ range frob"
      by (metis rangeI)
  qed auto
qed
end
text ‹
  The following type class describes a field with a surjective Frobenius endomorphism
  that is effectively computable. This includes all finite fields.
›
class inv_frob = surj_frob +
  fixes inv_frob_code :: "'a ⇒ 'a"
  assumes inv_frob_code: "inv_frob x = inv_frob_code x"
lemmas [code] = inv_frob_code
context finite_field
begin
subclass surj_frob
proof
  show "surj (frob :: 'a ⇒ 'a)"
    using inj_frob finite_UNIV by (simp add: finite_UNIV_inj_surj)
qed
end
lemma inv_frob_mod_ring [simp]: "inv_frob (x :: 'a :: prime_card mod_ring) = x"
  by (auto simp: frob_def)
instantiation mod_ring :: (prime_card) inv_frob
begin
definition inv_frob_code_mod_ring :: "'a mod_ring ⇒ 'a mod_ring" where
  "inv_frob_code_mod_ring x = x"
instance 
  by standard (auto simp: inv_frob_code_mod_ring_def)
end
subsection ‹Inverting the Frobenius endomorphism on polynomials›
text ‹
  If ‹K› is a field of prime characteristic ‹p› with a surjective Frobenius endomorphism,
  every polynomial ‹P› with ‹P' = 0› has a ‹p›-th root.
  To see that, let $\phi(a) = a^p$ denote the Frobenius endomorphism of ‹K›
  and its extension to ‹K[X]›.
  If ‹P' = 0› for some ‹P ∈ K[X]›, then ‹P› must be of the form
  \[P = a_0 + a_p x^p + a_{2p} x^{2p} + \ldots + a_{kp} x^{kp}\ .\]
  If we now set
  \[Q := \phi^{-1}(a_0) + \phi^{-1}(a_p) x + \phi^{-1}(a_{2p}) x^2 + \ldots + \phi^{-1}(a_{kp}) x^k\]
  we get $\phi(Q) = P$, i.e.\ $Q$ is the $p$-th root of $P(x)$.
›
lift_definition inv_frob_poly :: "'a :: field poly ⇒ 'a poly" is
  "λp i. if CHAR('a) = 0 then p i else inv_frob (p (i * CHAR('a)) :: 'a)"
proof goal_cases
  case (1 f)
  show ?case
  proof (cases "CHAR('a) > 0")
    case True
    from 1 obtain N where N: "f i = 0" if "i ≥ N" for i
      using cofinite_eq_sequentially eventually_sequentially by auto
    have "inv_frob (f (i * CHAR('a))) = 0" if "i ≥ N" for i
    proof -
      have "f (i * CHAR('a)) = 0"
      proof (rule N)
        show "N ≤ i * CHAR('a)"
          using that True 
          by (metis One_nat_def Suc_leI le_trans mult.right_neutral mult_le_mono2)
      qed
      thus "inv_frob (f (i * CHAR('a))) = 0"
        by (auto simp: power_0_left)
    qed
    thus ?thesis using True
      unfolding cofinite_eq_sequentially eventually_sequentially by auto
  qed (use 1 in auto)
qed
lemma coeff_inv_frob_poly [simp]:
  fixes p :: "'a :: field poly"
  assumes "CHAR('a) > 0"
  shows "poly.coeff (inv_frob_poly p) i = inv_frob (poly.coeff p (i * CHAR('a)))"
  using assms by transfer auto
lemma inv_frob_poly_0 [simp]: "inv_frob_poly 0 = 0"
  by transfer (auto simp: fun_eq_iff power_0_left)
lemma inv_frob_poly_1 [simp]: "inv_frob_poly 1 = 1"
  by transfer (auto simp: fun_eq_iff power_0_left)
lemma degree_inv_frob_poly_le:
  fixes p :: "'a :: field poly"
  assumes "CHAR('a) > 0"
  shows "Polynomial.degree (inv_frob_poly p) ≤ Polynomial.degree p div CHAR('a)"
proof (intro degree_le allI impI)
  fix i assume "Polynomial.degree p div CHAR('a) < i"
  hence "i * CHAR('a) > Polynomial.degree p"
    using assms div_less_iff_less_mult by blast
  thus "Polynomial.coeff (inv_frob_poly p) i = 0"
    by (simp add: coeff_eq_0 power_0_left assms)
qed
context
  assumes "SORT_CONSTRAINT('a :: comm_ring_1)"
  assumes prime_char: "prime CHAR('a)"
begin
lemma poly_power_prime_char_as_sum_of_monoms:
  fixes h :: "'a poly"
  shows "h ^ CHAR('a) = (∑i≤Polynomial.degree h. Polynomial.monom (Polynomial.coeff h i ^ CHAR('a)) (CHAR('a)*i))"
proof -
  have "h ^ CHAR('a) = (∑i≤Polynomial.degree h. Polynomial.monom (Polynomial.coeff h i) i) ^ CHAR('a)"
    by (simp add: poly_as_sum_of_monoms)
  also have "... = (∑i≤Polynomial.degree h. (Polynomial.monom (Polynomial.coeff h i) i) ^ CHAR('a))"
    by (simp add: freshmans_dream_sum prime_char)
  also have "... = (∑i≤Polynomial.degree h. Polynomial.monom (Polynomial.coeff h i ^ CHAR('a)) (CHAR('a)*i))"
  proof (rule sum.cong, rule)
    fix x assume x: "x ∈ {..Polynomial.degree h}"
    show "Polynomial.monom (Polynomial.coeff h x) x ^ CHAR('a) = Polynomial.monom (Polynomial.coeff h x ^ CHAR('a)) (CHAR('a) * x)"
      by (unfold poly_eq_iff, auto simp add: monom_power)
  qed
  finally show ?thesis .
qed
lemma coeff_of_prime_char_power [simp]:
  fixes y :: "'a poly"
  shows "poly.coeff (y ^ CHAR('a)) (i * CHAR('a)) = poly.coeff y i ^ CHAR('a)"
  using prime_char
  by (subst poly_power_prime_char_as_sum_of_monoms, subst Polynomial.coeff_sum)
     (auto intro: le_degree simp: power_0_left)
lemma coeff_of_prime_char_power':
  fixes y :: "'a poly"
  shows "poly.coeff (y ^ CHAR('a)) i =
           (if CHAR('a) dvd i then poly.coeff y (i div CHAR('a)) ^ CHAR('a) else 0)"
proof -
  have "poly.coeff (y ^ CHAR('a)) i =
          (∑j≤Polynomial.degree y. Polynomial.coeff (Polynomial.monom (Polynomial.coeff y j ^ CHAR('a)) (CHAR('a) * j)) i)"
    by (subst poly_power_prime_char_as_sum_of_monoms, subst Polynomial.coeff_sum) auto
  also have "… = (∑j∈(if CHAR('a) dvd i ∧ i div CHAR('a) ≤ Polynomial.degree y then {i div CHAR('a)} else {}).
                    Polynomial.coeff (Polynomial.monom (Polynomial.coeff y j ^ CHAR('a)) (CHAR('a) * j)) i)"
    by (intro sum.mono_neutral_right) (use prime_char in auto)
  also have "… = (if CHAR('a) dvd i then poly.coeff y (i div CHAR('a)) ^ CHAR('a) else 0)"
  proof (cases "CHAR('a) dvd i ∧ i div CHAR('a) > Polynomial.degree y")
    case True
    hence "Polynomial.coeff y (i div CHAR('a)) ^ CHAR('a) = 0"
      using prime_char by (simp add: coeff_eq_0 zero_power power_0_left)
    thus ?thesis
      by auto
  qed auto
  finally show ?thesis .
qed
end
context
  assumes "SORT_CONSTRAINT('a :: field)"
  assumes pos_char: "CHAR('a) > 0"
begin
interpretation field_prime_char "(/)" inverse "(*)" "1 :: 'a" "(+)" 0 "(-)" uminus
  rewrites "semiring_1.frob 1 (*) (+) (0 :: 'a) = frob" and
           "semiring_1.inv_frob 1 (*) (+) (0 :: 'a) = inv_frob" and
           "semiring_1.semiring_char 1 (+) 0 TYPE('a) = CHAR('a)"
proof unfold_locales
  have *: "class.semiring_1 (1 :: 'a) (*) (+) 0" ..
  have [simp]: "semiring_1.of_nat (1 :: 'a) (+) 0 = of_nat"
    by (auto simp: of_nat_def semiring_1.of_nat_def[OF *])
  thus "∃n>0. semiring_1.of_nat (1 :: 'a) (+) 0 n = 0"
    by (intro exI[of _ "CHAR('a)"]) (use pos_char in auto)
  show "semiring_1.semiring_char 1 (+) 0 TYPE('a) = CHAR('a)"
    by (simp add: fun_eq_iff semiring_char_def semiring_1.semiring_char_def[OF *])
  show [simp]: "semiring_1.frob (1 :: 'a) (*) (+) 0 = frob"
    by (simp add: frob_def semiring_1.frob_def[OF *] fun_eq_iff
                  power.power_def power_def semiring_char_def semiring_1.semiring_char_def[OF *])
  show "semiring_1.inv_frob (1 :: 'a) (*) (+) 0 = inv_frob"
    by (simp add: inv_frob_def semiring_1.inv_frob_def[OF *] fun_eq_iff)
qed
lemma inv_frob_poly_power': "inv_frob_poly (p ^ CHAR('a) :: 'a poly) = p"
  using prime_CHAR_semidom[OF pos_char] pos_char
  by (auto simp: poly_eq_iff simp flip: frob_def)
lemma inv_frob_poly_power:
  fixes p :: "'a poly"
  assumes "is_nth_power CHAR('a) p" and "n = CHAR('a)"
  shows   "inv_frob_poly p ^ CHAR('a) = p"
proof -
  from assms(1) obtain q where q: "p = q ^ CHAR('a)"
    by (elim is_nth_powerE)
  thus ?thesis using assms
    by (simp add: q inv_frob_poly_power')
qed
theorem pderiv_eq_0_imp_nth_power:
  assumes "pderiv (p :: 'a poly) = 0"
  assumes [simp]: "surj (frob :: 'a ⇒ 'a)"
  shows   "is_nth_power CHAR('a) p"
proof -
  have *: "poly.coeff p n = 0" if n: "¬CHAR('a) dvd n" for n
  proof (cases "n = 0")
    case False
    have "poly.coeff (pderiv p) (n - 1) = of_nat n * poly.coeff p n"
      using False by (auto simp: coeff_pderiv)
    with assms and n show "poly.coeff p n = 0"
      by (auto simp: of_nat_eq_0_iff_char_dvd)
  qed (use that in auto)
  have **: "inv_frob_poly p ^ CHAR('a) = p"
  proof (rule poly_eqI)
    fix n :: nat
    show "poly.coeff (inv_frob_poly p ^ CHAR('a)) n = poly.coeff p n"
      using * CHAR_dvd_CARD[where ?'a = 'a] 
      by (subst coeff_of_prime_char_power')
         (auto simp: poly_eq_iff frob_def [symmetric]
                     coeff_of_prime_char_power'[where ?'a = 'a] simp flip: power_mult)
  qed
  
  show ?thesis
    by (subst **[symmetric]) auto
qed
end
subsection ‹Code generation›
text ‹
  We now also make this notion of ``taking the ‹p›-th root of a polynomial''
  executable. For this, we need an auxiliary function that takes a list
  $[x_0, \ldots, x_m]$ and returns the list of every ‹n›-th element, i.e.\ it
  throws away all elements except those $x_i$ where $i$ is a multiple of $n$.
›
fun take_every :: "nat ⇒ 'a list ⇒ 'a list" where
  "take_every _ [] = []"
| "take_every n (x # xs) = x # take_every n (drop (n - 1) xs)"
lemma take_every_0 [simp]: "take_every 0 xs = xs"
  by (induction xs) auto
lemma take_every_1 [simp]: "take_every (Suc 0) xs = xs"
  by (induction xs) auto
lemma int_length_take_every: "n > 0 ⟹ int (length (take_every n xs)) = ceiling (length xs / n)"
proof (induction n xs rule: take_every.induct)
  case (2 n x xs)
  show ?case
  proof (cases "Suc (length xs) ≥ n")
    case True
    thus ?thesis using 2
      by (auto simp: dvd_imp_le of_nat_diff diff_divide_distrib split: if_splits)
  next
    case False
    hence "⌈(1 + real (length xs)) / real n⌉ = 1"
      by (intro ceiling_unique) auto
    thus ?thesis using False
      by auto
  qed
qed auto
lemma length_take_every:
  "n > 0 ⟹ length (take_every n xs) = nat (ceiling (length xs / n))"
  using int_length_take_every[of n xs] by simp
lemma take_every_nth [simp]:
  "n > 0 ⟹ i < length (take_every n xs) ⟹ take_every n xs ! i = xs ! (n * i)"
proof (induction n xs arbitrary: i rule: take_every.induct)
  case (2 n x xs i)
  show ?case
  proof (cases i)
    case (Suc j)
    have "n - Suc 0 ≤ length xs"
      using Suc "2.prems" nat_le_linear by force
    hence "drop (n - Suc 0) xs ! (n * j) = xs ! (n - 1 + n * j)"
      using Suc by (subst nth_drop) auto
    also have "n - 1 + n * j = n + n * j - 1"
      using ‹n > 0› by linarith
    finally show ?thesis
      using "2.IH"[of j] "2.prems" Suc by simp
  qed auto
qed auto
lemma coeffs_eq_strip_whileI:
  assumes "⋀i. i < length xs ⟹ Polynomial.coeff p i = xs ! i"
  assumes "p ≠ 0 ⟹ length xs > Polynomial.degree p"
  shows   "Polynomial.coeffs p = strip_while ((=) 0) xs"
proof (rule coeffs_eqI)
  fix n :: nat
  show "Polynomial.coeff p n = nth_default 0 (strip_while ((=) 0) xs) n"
    using assms
    by (metis coeff_0 coeff_Poly_eq coeffs_Poly le_degree nth_default_coeffs_eq 
      nth_default_eq_dflt_iff nth_default_nth order_le_less_trans)
qed auto
text ‹This implements the code equation for ‹inv_frob_poly›.›
lemma inv_frob_poly_code [code]:
  "Polynomial.coeffs (inv_frob_poly (p :: 'a :: field_prime_char poly)) =
     (if CHAR('a) = 0 then Polynomial.coeffs p else 
        map inv_frob (strip_while ((=) 0) (take_every CHAR('a) (Polynomial.coeffs p))))"
    (is "_ = If _ _ ?rhs")
proof (cases "CHAR('a) = 0 ∨ p = 0")
  case False
  from False have "p ≠ 0"
    by auto
  have "Polynomial.coeffs (inv_frob_poly p) =
          strip_while ((=) 0) (map inv_frob (take_every CHAR('a) (Polynomial.coeffs p)))"
  proof (rule coeffs_eq_strip_whileI)
    fix i assume i: "i < length (map inv_frob (take_every CHAR('a) (Polynomial.coeffs p)))"
    show "Polynomial.coeff (inv_frob_poly p) i = map inv_frob (take_every CHAR('a) (Polynomial.coeffs p)) ! i"
    proof -
      have "i < length (take_every CHAR('a) (Polynomial.coeffs p))"
        using i by simp
      also have "length (take_every CHAR('a) (Polynomial.coeffs p)) =
                   nat ⌈(Polynomial.degree p + 1) / real CHAR('a)⌉"
        using False CHAR_pos[where ?'a = 'a]
        by (simp add: length_take_every length_coeffs)
      finally have "i < real (Polynomial.degree p + 1) / real CHAR('a)"
        by linarith
      hence "real i * real CHAR('a) < real (Polynomial.degree p + 1)"
        using False CHAR_pos[where ?'a = 'a] by (simp add: field_simps)
      hence "i * CHAR('a) ≤ Polynomial.degree p"
        unfolding of_nat_mult [symmetric] by linarith
      hence "Polynomial.coeffs p ! (i * CHAR('a)) = Polynomial.coeff p (i * CHAR('a))"
        using False by (intro coeffs_nth) (auto simp: length_take_every)
      thus ?thesis using False i CHAR_pos[where ?'a = 'a]
        by (auto simp: nth_default_def mult.commute)
    qed
  next
    assume nz: "inv_frob_poly p ≠ 0"
    have "Polynomial.degree (inv_frob_poly p) ≤ Polynomial.degree p div CHAR('a)"
      by (rule degree_inv_frob_poly_le) (fact CHAR_pos)
    also have "… < nat ⌈(real (Polynomial.degree p) + 1) / real CHAR('a)⌉"
      using CHAR_pos[where ?'a = 'a]
      by (metis div_less_iff_less_mult linorder_not_le nat_le_real_less of_nat_0_less_iff
                of_nat_ceiling of_nat_mult pos_less_divide_eq)
    also have "… = length (take_every CHAR('a) (Polynomial.coeffs p))"
      using CHAR_pos[where ?'a = 'a] ‹p ≠ 0› by (simp add: length_take_every length_coeffs add_ac)
    finally show "length (map inv_frob (take_every CHAR('a) (Polynomial.coeffs p))) > Polynomial.degree (inv_frob_poly p)"
      by simp_all
  qed
  also have "strip_while ((=) 0) (map inv_frob (take_every CHAR('a) (Polynomial.coeffs p))) =
             map inv_frob (strip_while ((=) 0 ∘ inv_frob) (take_every CHAR('a) (Polynomial.coeffs p)))"
    by (rule strip_while_map)
  also have "(=) 0 ∘ inv_frob = (=) (0 :: 'a)"
    by (auto simp: fun_eq_iff)
  finally show ?thesis
    using False by metis
qed auto
subsection ‹Perfect fields›
text ‹
  We now introduce perfect fields. The textbook definition of a perfect field
  is that every irreducible polynomial is separable, i.e.\ if a polynomial $P$
  has no non-trivial divisors then $\text{gcd}(P, P') = 0$.
  For technical reasons, this is somewhat difficult to express in Isabelle/HOL's typeclass system.
  We therefore use the following much simpler equivalent definition (and prove equivalence later):
  a field is perfect if it either has characteristic 0 or its Frobenius endomorphism
  is surjective.
›
class perfect_field = field +
  assumes perfect_field: "CHAR('a) = 0 ∨ surj (frob :: 'a ⇒ 'a)"
context field_char_0
begin
subclass perfect_field
  by standard auto
end
context surj_frob
begin
subclass perfect_field
  by standard auto
end
context alg_closed_field
begin
subclass perfect_field
  by standard (use alg_closed_surj_frob in auto)
end
theorem irreducible_imp_pderiv_nonzero:
  assumes "irreducible (p :: 'a :: perfect_field poly)"
  shows   "pderiv p ≠ 0"
proof (cases "CHAR('a) = 0")
  case True
  interpret A: semiring_1 "1 :: 'a" "(*)" "(+)" "0 :: 'a" ..
  have *: "class.semiring_1 (1 :: 'a) (*) (+) 0" ..
  interpret A: field_char_0 "(/)" inverse "(*)" "1 :: 'a" "(+)" 0 "(-)" uminus
  proof
    have "inj (of_nat :: nat ⇒ 'a)"
      by (auto simp: inj_on_def of_nat_eq_iff_cong_CHAR True)
    also have "of_nat = semiring_1.of_nat (1 :: 'a) (+) 0"
      by (simp add: of_nat_def [abs_def] semiring_1.of_nat_def [OF *, abs_def])
    finally show "inj …" .
  qed
  show ?thesis
  proof
    assume "pderiv p = 0"
    hence **: "poly.coeff p (Suc n) = 0" for n
      by (auto simp: poly_eq_iff coeff_pderiv of_nat_eq_0_iff_char_dvd True simp del: of_nat_Suc)
    have "poly.coeff p n = 0" if "n > 0" for n
      using **[of "n - 1"] that by (cases n) auto
    hence "Polynomial.degree p = 0"
      by force
    thus False
      using assms by force
  qed
next
  case False
  hence [simp]: "surj (frob :: 'a ⇒ 'a)"
    by (meson perfect_field)
  interpret A: field_prime_char "(/)" inverse "(*)" "1 :: 'a" "(+)" 0 "(-)" uminus
  proof
    have *: "class.semiring_1 1 (*) (+) (0 :: 'a)" ..
    have "semiring_1.of_nat 1 (+) (0 :: 'a) = of_nat"
      by (simp add: fun_eq_iff of_nat_def semiring_1.of_nat_def[OF *])
    thus "∃n>0. semiring_1.of_nat 1 (+) 0 n = (0 :: 'a)"
      by (intro exI[of _ "CHAR('a)"]) (use False in auto)
  qed
  show ?thesis
  proof
    assume "pderiv p = 0"
    hence "is_nth_power CHAR('a) p"
      using pderiv_eq_0_imp_nth_power[of p] surj_frob False by simp
    then obtain q where "p = q ^ CHAR('a)"
      by (elim is_nth_powerE)
    with assms show False
      by auto
  qed
qed
corollary irreducible_imp_separable:
  assumes "irreducible (p :: 'a :: perfect_field poly)"
  shows   "coprime p (pderiv p)"
proof (rule coprimeI)
  fix q assume q: "q dvd p" "q dvd pderiv p"
  have "¬p dvd q"
  proof
    assume "p dvd q"
    hence "p dvd pderiv p"
      using q dvd_trans by blast
    hence "Polynomial.degree p ≤ Polynomial.degree (pderiv p)"
      by (rule dvd_imp_degree_le) (use assms irreducible_imp_pderiv_nonzero in auto)
    also have "… ≤ Polynomial.degree p - 1"
      using degree_pderiv_le by auto
    finally have "Polynomial.degree p = 0"
      by simp
    with assms show False
      using irreducible_imp_pderiv_nonzero is_unit_iff_degree by blast
  qed
  with ‹q dvd p› show "is_unit q"
    using assms comm_semiring_1_class.irreducibleD' by blast
qed
 
end