Theory GF_p

section ‹The prime fields ‹GF(p) = ℤ/pℤ››

theory GF_p
  imports Ring_Theory Poly_Ring Poly_Ideal "HOL-Computational_Algebra.Primes"
begin

subsection ‹The residue rings ‹ℤ/nℤ››

text ‹‹Ring_Theory› suppresses HOL's arithmetic notation; restore it locally for the modular
  arithmetic below.›
notation plus (infixl ‹+› 65)
notation minus (infixl ‹-› 65)
unbundle uminus_syntax

text ‹For any @{term "n > 1"}, the integers modulo @{term n} form a commutative ring: carrier
  @{term "{0..<n}"} of @{typ nat}, arithmetic modulo @{term n}.  This is the abstract residue ring
  ‹ℤ/nℤ›; when @{term n} is prime it is in fact a field, developed in ‹GF_p›.  The bound
  @{term "n > 1"} ensures both @{term 0} and @{term 1} lie in @{term "{0..<n}"} and are distinct
  in @{typ nat}; the trivial ring case @{term "n = 1"} is not modelled by this carrier.›

definition gfp_add :: "nat ⇒ nat ⇒ nat ⇒ nat"
  where "gfp_add n x y = (x + y) mod n"

definition gfp_mult :: "nat ⇒ nat ⇒ nat ⇒ nat"
  where "gfp_mult n x y = (x * y) mod n"

context
  fixes n :: nat
  assumes n_gt_1: "n > 1"
begin

lemma n_gt_0: "n > 0" using n_gt_1 by simp

text ‹Closure: results of modular arithmetic land in @{term "{0..<n}"}.›
lemma gfp_add_closed [simp]: "gfp_add n x y ∈ {0..<n}"
  using n_gt_0 by (simp add: gfp_add_def)

lemma gfp_mult_closed [simp]: "gfp_mult n x y ∈ {0..<n}"
  using n_gt_0 by (simp add: gfp_mult_def)

lemma zero_mem: "(0::nat) ∈ {0..<n}" and one_mem: "(1::nat) ∈ {0..<n}"
  using n_gt_1 by auto

text ‹The additive part is an abelian group: associative and commutative modular addition, with
  @{term "n - x"} (reduced) as the inverse of @{term x}.›
lemma gfp_add_group: "Group {0..<n} (gfp_add n) 0"
proof (rule GroupI)
  show "⋀x y z. ⟦ x ∈ {0..<n}; y ∈ {0..<n}; z ∈ {0..<n} ⟧
          ⟹ gfp_add n (gfp_add n x y) z = gfp_add n x (gfp_add n y z)"
    by (simp add: gfp_add_def mod_add_left_eq mod_add_right_eq add.assoc)
next
  fix x assume x: "x ∈ {0..<n}"
  then have key: "(x + (n - x) mod n) mod n = 0"
    by (simp_all add: mod_add_right_eq)
  then show "∃y∈{0..<n}. gfp_add n x y = 0 ∧ gfp_add n y x = 0"
    by (metis add.commute add.right_neutral gfp_add_closed gfp_add_def)
qed (use zero_mem n_gt_0 in ‹simp_all add: add.commute gfp_add_def›)

lemma gfp_add_commutative_monoid: "commutative_monoid {0..<n} (gfp_add n) 0"
  unfolding commutative_monoid_def
  by (metis Group_def add.commute commutative_monoid_axioms.intro gfp_add_def gfp_add_group)

lemma gfp_additive: "Abelian_Group {0..<n} (gfp_add n) 0"
  by (rule Abelian_Group.intro [OF gfp_add_group gfp_add_commutative_monoid])

text ‹The multiplicative part is a commutative monoid with unit @{term 1}.›
lemma gfp_multiplicative: "commutative_monoid {0..<n} (gfp_mult n) 1"
proof
  show "⋀x y z. ⟦ x ∈ {0..<n}; y ∈ {0..<n}; z ∈ {0..<n} ⟧
          ⟹ gfp_mult n (gfp_mult n x y) z = gfp_mult n x (gfp_mult n y z)"
    by (simp add: gfp_mult_def mod_mult_left_eq mod_mult_right_eq mult.assoc)
qed (use one_mem n_gt_1 in ‹simp_all add: mult.commute gfp_mult_def›)

lemma zfact_ring: "Ring {0..<n} (gfp_add n) (gfp_mult n) 0 1"
proof (intro Ring.intro gfp_additive)
  show "Monoid {0..<n} (gfp_mult n) 1"
    by (rule commutative_monoid.axioms(1) [OF gfp_multiplicative])
  show "Ring_axioms {0..<n} (gfp_add n) (gfp_mult n)"
    by unfold_locales
       (simp_all add: gfp_add_def gfp_mult_def mod_mult_left_eq mod_mult_right_eq
                      add_mult_distrib add_mult_distrib2 mod_add_left_eq mod_add_right_eq)
qed

lemma zfact_commutative_ring: "commutative_ring {0..<n} (gfp_add n) (gfp_mult n) 0 1"
  by (rule commutative_ring.intro [OF zfact_ring gfp_multiplicative])

text ‹The residue ring @{text "ℤ/nℤ"} has exactly @{term n} elements.›
lemma card_zfact: "card {0..<n} = n" by simp

end

text ‹Hence, for every @{term "n > 1"}, the locale @{locale commutative_ring} is non-vacuous with
  a witness of cardinality @{term n}.›


subsection ‹The residue ring as a field›

text ‹For a prime @{term p}, the residue ring ‹ZFact› becomes a field:
  the only extra step over the general @{term "n > 0"} case is that every nonzero residue has a
  multiplicative inverse, which follows from Bézout's identity since @{term p} is prime.

  Carrier @{term "{0..<p}"} of @{typ nat}, arithmetic modulo @{term p}, arithmetic operations
  @{const gfp_add} and @{const gfp_mult} shared with the ring-level development in @{text ZFact}.›

context
  fixes p :: nat
  assumes prime_p: "prime p"
begin

lemma p_gt_1: "p > 1" using prime_p by (simp add: prime_nat_iff)
lemma p_gt_0: "p > 0" using p_gt_1 by simp

text ‹∗‹Inverses.›  For @{term "a ∈ {1..<p}"}, @{term a} is coprime to the prime @{term p}, so
  Bézout gives integers with @{text "u⋅a + v⋅p = 1"}; reducing @{text "u"} modulo @{term p}
  yields a multiplicative inverse of @{term a}.›
lemma gfp_inverse_exists:
  assumes a: "a ∈ {0..<p}" and anz: "a ≠ 0"
  shows "∃b∈{0..<p}. gfp_mult p a b = 1"
proof -
  have ap: "a < p" and a1: "a ≥ 1" using a anz by auto
  ― ‹@{term p} prime and @{term "a < p"}, @{term "a ≠ 0"}, so @{term p} does not divide @{term a}.›
  have ndvd: "¬ p dvd a" using ap anz by (simp add: nat_dvd_not_less)
  then have "gcd a p = 1"
    by (metis gcd_unique prime_nat_iff prime_p)
  then have cop: "coprime (int a) (int p)" by (simp add: coprime_iff_gcd_eq_1)
  ― ‹Bézout: @{text "u⋅a + v⋅p = 1"} for some integers @{text u}, @{text v}.›
  obtain u v :: int where uv: "u * int a + v * int p = 1"
    using cop bezout_int by (metis coprime_imp_gcd_eq_1)
  define b where "b = nat (u mod int p)"
  have brange: "b ∈ {0..<p}" using p_gt_0 by (simp add: b_def) (simp add: nat_less_iff)
  ― ‹Then @{text "a⋅b ≡ a⋅u ≡ 1 (mod p)"}.›
  have "int (gfp_mult p a b) = (int a * u) mod int p"
    by (simp add: b_def gfp_mult_def mod_mult_right_eq of_nat_mod p_gt_0)
  also have "… = (1 - v * int p + v * int p) mod int p"
    by (metis diff_add_cancel mod_mult_self1 mult.commute uv)
  also have "… = 1" using p_gt_1 by simp
  finally show ?thesis using brange
    using of_nat_eq_1_iff by blast
qed

lemma gfp_field: "Field {0..<p} (gfp_add p) (gfp_mult p) 0 1"
proof -
  interpret R: commutative_ring "{0..<p}" "gfp_add p" "gfp_mult p" 0 1
    using zfact_commutative_ring[OF p_gt_1] .
  show ?thesis
  proof
    show "(1::nat) ≠ 0" using p_gt_1 by simp
    show "⋀a. a ∈ {0..<p} ⟹ a ≠ 0 ⟹ R.multiplicative.invertible a"
      by (metis R.multiplicative.invertibleI gfp_inverse_exists gfp_mult_def mult.commute)
  qed
qed

text ‹The prime field has exactly @{term p} elements.›
lemma card_gfp: "card {0..<p} = p" by simp

end

text ‹Hence prime fields of every prime order exist --- in particular the @{locale Field} locale is
  non-vacuous for every prime cardinality.›

subsection ‹The two-element field as a non-vacuity witness›

text ‹
  A concrete field with two elements, witnessing that the @{locale Field} and
  @{locale integral_domain} locales (and hence the abstract @{typ "'a ring"} type as a
  field) are non-vacuous.  Carrier @{term "{0, 1}"} of @{typ nat}.
›

definition gf2_add :: "nat ⇒ nat ⇒ nat" where "gf2_add x y ≡ (x + y) mod 2"

definition gf2_mult :: "nat ⇒ nat ⇒ nat" where "gf2_mult x y ≡ (x * y) mod 2"

text ‹Enumeration helper: membership in the two-element carrier.›
lemma gf2_cases: "x ∈ {0, 1::nat} ⟹ x = 0 ∨ x = 1" by auto

lemma gf2_add_simps [simp]:
  "gf2_add 0 0 = 0" "gf2_add 0 1 = 1" "gf2_add 1 0 = 1" "gf2_add 1 1 = 0"
  by (simp_all add: gf2_add_def)

lemma gf2_mult_simps [simp]:
  "gf2_mult 0 0 = 0" "gf2_mult 0 1 = 0" "gf2_mult 1 0 = 0" "gf2_mult 1 1 = 1"
  by (simp_all add: gf2_mult_def)

lemma gf2_add_closed [simp]: "⟦ x ∈ {0,1::nat}; y ∈ {0,1} ⟧ ⟹ gf2_add x y ∈ {0,1}"
  by (auto simp: gf2_add_def)

lemma gf2_mult_closed [simp]: "⟦ x ∈ {0,1::nat}; y ∈ {0,1} ⟧ ⟹ gf2_mult x y ∈ {0,1}"
  by (auto simp: gf2_mult_def)

text ‹The additive part is a group (each element is its own inverse), via @{thm [source] GroupI}.›
lemma gf2_add_group: "Group {0, 1::nat} gf2_add 0"
proof (rule GroupI)
  fix u :: nat assume u: "u ∈ {0,1}"
  then have "gf2_add u u = 0" by (auto simp: gf2_add_def)
  with u show "∃v ∈ {0,1::nat}. gf2_add u v = 0 ∧ gf2_add v u = 0" by blast
qed (auto simp: gf2_add_def)

lemma gf2_add_commutative_monoid: "commutative_monoid {0, 1::nat} gf2_add 0"
  by unfold_locales (auto simp: gf2_add_def)

text ‹The additive part is therefore an abelian group.›
lemma gf2_additive: "Abelian_Group {0, 1::nat} gf2_add 0"
  by (rule Abelian_Group.intro [OF gf2_add_group gf2_add_commutative_monoid])

text ‹The multiplicative part is a commutative monoid.›
lemma gf2_multiplicative: "commutative_monoid {0, 1::nat} gf2_mult 1"
  by unfold_locales (auto simp: gf2_mult_def)

lemma gf2_ring: "Ring {0, 1::nat} gf2_add gf2_mult 0 1"
proof (rule Ring.intro)
  show "Abelian_Group {0,1::nat} gf2_add 0" by (rule gf2_additive)
  show "Monoid {0,1::nat} gf2_mult 1" by (rule commutative_monoid.axioms(1) [OF gf2_multiplicative])
  show "Ring_axioms {0,1::nat} gf2_add gf2_mult" by unfold_locales (auto simp: gf2_add_def gf2_mult_def)
qed

lemma gf2_commutative_ring: "commutative_ring {0, 1::nat} gf2_add gf2_mult 0 1"
  by (rule commutative_ring.intro [OF gf2_ring gf2_multiplicative])

text ‹The two-element field.  This is the single witness the downstream constructions consume (via
  ‹interpretation … by (rule gf2_field)› in ‹GF4› and ‹GF8›); it also witnesses non-vacuity of the
  @{locale Field}, @{locale integral_domain} and @{locale commutative_ring} locales, the last two by
  the locale hierarchy (a @{locale Field} is in particular an @{locale integral_domain}).›
lemma gf2_field: "Field {0, 1::nat} gf2_add gf2_mult 0 1"
proof -
  interpret R: commutative_ring "{0,1::nat}" gf2_add gf2_mult 0 1 by (rule gf2_commutative_ring)
  show ?thesis
  proof 
    show "(1::nat) ≠ 0" by simp
  next
    fix a :: nat assume a: "a ∈ {0,1}" "a ≠ 0"
    then have a1: "a = 1" by auto
    then have "gf2_mult a a = 1" by (simp add: gf2_mult_def)
    then show "R.multiplicative.invertible a"
      using a1 by (intro R.multiplicative.invertibleI [where v = a]) auto
  qed
qed

subsection ‹The two-element field with polynomial machinery›

interpretation GF2: Field "{0, 1::nat}" gf2_add gf2_mult 0 1
  by (rule gf2_field)

lemma GF2_poly_const_carrier [simp, intro]: "GF2.poly_const 1 ∈ GF2.poly_carrier"
  by (rule GF2.poly_const_closed) simp

lemma GF2_monom_carrier [simp, intro]: "GF2.monom 1 n ∈ GF2.poly_carrier"
  by (rule GF2.monom_closed) simp


subsection ‹The 3-element prime field with polynomial machinery›

text ‹A single, canonical interpretation of the @{locale Field} locale on ‹GF(3)›.›

interpretation GF3: Field "{0..<3::nat}" "gfp_add 3" "gfp_mult 3" 0 1
  using gfp_field[of 3] by simp

lemma GF3_poly_const_carrier [simp, intro]: "GF3.poly_const 1 ∈ GF3.poly_carrier"
  by (rule GF3.poly_const_closed) simp

lemma GF3_monom_carrier [simp, intro]: "GF3.monom 1 n ∈ GF3.poly_carrier"
  by (rule GF3.monom_closed) simp

text ‹An end-to-end witness for the carrier-set field machinery, over a non-binary prime
  field: the polynomial ‹X2 + 1› is irreducible over ‹GF(3)› (it has no root there, as ‹-1› is not
  a square modulo 3), so by Kronecker's theorem the quotient ‹GF(3)[X] / (X2 + 1)› is a field, and
  by @{thm [source] Field.card_poly_quotient} it has ‹32 = 9› elements --- the field ‹GF(9)›.›

text ‹The witness polynomial ‹Ψ = X2 + 1› over ‹GF(3)›.›
definition Psi :: "nat ⇒ nat"
  where "Psi ≡ GF3.poly_add (GF3.monom 1 2) (GF3.poly_const 1)"

lemma Psi_coeff: "Psi i = (if i = 0 ∨ i = 2 then 1 else 0)"
  by (simp add: Psi_def GF3.poly_add_def GF3.monom_def GF3.poly_const_def gfp_add_def)

lemma Psi_closed: "Psi ∈ GF3.poly_carrier"
  unfolding Psi_def by (intro GF3.poly_add_closed GF3_monom_carrier GF3_poly_const_carrier)

text ‹@{term Psi} has degree exactly @{text 2}.›
lemma degree_Psi: "GF3.degree Psi = 2"
proof (rule antisym)
  show "GF3.degree Psi ≤ 2"
    by (rule GF3.degree_leI[OF Psi_closed]) (simp add: Psi_coeff)
  have "Psi 2 ≠ 0" by (simp add: Psi_coeff)
  moreover have "finite {i. Psi i ≠ 0}" using Psi_closed by (rule GF3.poly_carrier_finite)
  ultimately show "2 ≤ GF3.degree Psi"
    by (simp add: GF3.degree_def) (metis (mono_tags, lifting) Max_ge empty_iff mem_Collect_eq)
qed

text ‹@{term Psi} has no root in @{term "{0..<3::nat}"}: ‹α2 + 1› is @{text 1}, @{text 2}, @{text 2}
  at ‹α = 0, 1, 2› respectively --- never @{text 0}.›
lemma Psi_no_root: "α ∈ {0..<3::nat} ⟹ GF3.eval α Psi ≠ 0"
proof -
  assume a: "α ∈ {0..<3::nat}"
  ― ‹Evaluate term by term.›
  have "GF3.eval α Psi = gfp_add 3 (GF3.eval α (GF3.monom 1 2)) (GF3.eval α (GF3.poly_const 1))"
    unfolding Psi_def by (rule GF3.eval_add[OF GF3_monom_carrier GF3_poly_const_carrier a])
  also have "GF3.eval α (GF3.monom 1 2) = gfp_mult 3 1 (GF3.rpow α 2)"
    using GF3.eval_monom a by blast
  also have "GF3.eval α (GF3.poly_const 1) = 1"
    using GF3.eval_const a by blast
  finally have ev: "GF3.eval α Psi = gfp_add 3 (gfp_mult 3 1 (GF3.rpow α 2)) 1" .
  ― ‹Compute the square: @{text "α2 = α⋅α"}.›
  have r2: "GF3.rpow α 2 = gfp_mult 3 α (gfp_mult 3 α 1)"
    unfolding GF3.rpow_def by (simp add: eval_nat_numeral)
  have val: "GF3.eval α Psi = Suc (α * α mod 3) mod 3"
    using ev r2 by (simp add: gfp_add_def gfp_mult_def mod_mult_right_eq mod_mult_left_eq)
  ― ‹Check the three residues @{text "α = 0, 1, 2"} explicitly.›
  have "α = 0 ∨ α = 1 ∨ α = 2" using a by auto
  then show "GF3.eval α Psi ≠ 0" using val by auto
qed

text ‹Therefore @{term Psi} is irreducible over @{text "GF(3)"}.›
theorem Psi_irreducible: "GF3.poly_irreducible Psi"
  using GF3.degree23_no_root_irreducible[OF Psi_closed] degree_Psi Psi_no_root by blast

subsection ‹The four-element field ‹GF(4)› via Kronecker's construction›

text ‹The polynomial ‹X2 + X + 1› is irreducible over the two-element field ‹GF(2)› (it has no root there), 
  so by Kronecker's theorem (@{thm [source] Field.poly_quotient_field}) the quotient
  ‹GF(2)[X] / (X2 + X + 1)› is a field --- the four-element field ‹GF(4)›.›

text ‹The witness polynomial ‹Φ = X2 + X + 1› over ‹GF(2)›.›
definition Phi :: "nat ⇒ nat"
  where "Phi ≡ GF2.poly_add (GF2.poly_add (GF2.monom 1 2) (GF2.monom 1 1)) (GF2.poly_const 1)"

text ‹Its coefficients: @{text 1} at degrees @{text 0}, @{text 1}, @{text 2}, and @{text 0} above.›
lemma Phi_coeff: "Phi i = (if i ≤ 2 then 1 else 0)"
  by (simp add: Phi_def GF2.poly_add_def GF2.monom_def GF2.poly_const_def gf2_add_def)

lemma Phi_closed: "Phi ∈ GF2.poly_carrier"
  unfolding Phi_def by (intro GF2.poly_add_closed GF2_monom_carrier GF2_poly_const_carrier)

text ‹@{term Phi} has degree exactly @{text 2}.›
lemma degree_Phi: "GF2.degree Phi = 2"
proof (rule antisym)
  show "GF2.degree Phi ≤ 2"
    using GF2.degree_le_iff Phi_closed Phi_coeff by auto
  have "finite {i. Phi i ≠ 0}" using Phi_closed by (rule GF2.poly_carrier_finite)
  then show "2 ≤ GF2.degree Phi"
    using GF2.coeff_gt_degree Phi_closed Phi_coeff linorder_not_le by fastforce
qed

text ‹@{term Phi} has no root in @{term "{0, 1::nat}"}: both candidate values evaluate to
  @{text 1}, since ‹X2 + X + 1› at @{text 0} is @{text 1} and at @{text 1} is
  ‹1 + 1 + 1 = 1› modulo 2.›
lemma Phi_no_root:
  assumes a: "α ∈ {0, 1::nat}"
  shows "GF2.eval α Phi ≠ 0"
proof -
  have sum2: "GF2.poly_add (GF2.monom 1 2) (GF2.monom 1 1) ∈ GF2.poly_carrier"
    by (intro GF2.poly_add_closed GF2_monom_carrier)
  ― ‹Evaluate term by term, using additivity and @{thm [source] GF2.eval_monom}.›
  have "GF2.eval α Phi
        = gf2_add (GF2.eval α (GF2.poly_add (GF2.monom 1 2) (GF2.monom 1 1))) (GF2.eval α (GF2.poly_const 1))"
    unfolding Phi_def using GF2.eval_add[OF sum2 GF2_poly_const_carrier assms] .
  also have "… = gf2_add (gf2_add (gf2_mult 1 (GF2.rpow α 2)) (gf2_mult 1 (GF2.rpow α 1))) 1"
    using GF2.eval_add[OF GF2_monom_carrier GF2_monom_carrier assms]
          GF2.eval_const[OF assms] GF2.eval_monom[OF _ assms] by simp
  finally have "GF2.eval α Phi
        = gf2_add (gf2_add (gf2_mult 1 (GF2.rpow α 2)) (gf2_mult 1 (GF2.rpow α 1))) 1" .
  ― ‹Compute the powers: @{text "α2 = α⋅α"}, @{text "α1 = α"}.›
  then show "GF2.eval α Phi ≠ 0"
    using gf2_cases[OF a] gf2_add_simps(4) gf2_mult_simps(4) 
    unfolding GF2.rpow_def by (fastforce simp add: eval_nat_numeral)
qed

text ‹Therefore @{term Phi} is irreducible over @{text "GF(2)"}.›
theorem Phi_irreducible: "GF2.poly_irreducible Phi"
  using GF2.degree23_no_root_irreducible[OF Phi_closed] degree_Phi Phi_no_root by blast

text ‹∗‹Kronecker.›  The principal ideal @{text "(Φ)"} is maximal in @{text "GF(2)[X]"}, so the
  quotient @{text "GF(2)[X]/(Φ)"} is a field --- a four-element field, @{text "GF(4)"}.›
theorem GF4_is_field:
  "ideal_in_comm_ring (GF2.poly_pideal Phi) GF2.poly_carrier GF2.poly_add GF2.poly_mult GF2.poly_zero GF2.poly_one
   ∧ Ideal.maximal_ideal (GF2.poly_pideal Phi) GF2.poly_carrier GF2.poly_add GF2.poly_mult GF2.poly_zero GF2.poly_one"
  using GF2.poly_quotient_field[OF Phi_irreducible] .

subsection ‹The eight-element field ‹GF(8)› via Kronecker's construction›

text ‹A cubic witness for the carrier-set field machinery: the polynomial ‹X3 + X + 1› is
  irreducible over ‹GF(2)› (degree 3 with no root), so by Kronecker's theorem the quotient
  ‹GF(2)[X] / (X3 + X + 1)› is a field, and by @{thm [source] Field.card_poly_quotient} it has
  ‹23 = 8› elements --- the field ‹GF(8)›.›

text ‹The witness polynomial ‹Θ = X3 + X + 1› over ‹GF(2)›.›
definition Theta :: "nat ⇒ nat"
  where "Theta = GF2.poly_add (GF2.poly_add (GF2.monom 1 3) (GF2.monom 1 1)) (GF2.poly_const 1)"

lemma Theta_coeff: "Theta i = (if i = 0 ∨ i = 1 ∨ i = 3 then 1 else 0)"
  by (simp add: Theta_def GF2.poly_add_def GF2.monom_def GF2.poly_const_def gf2_add_def)

lemma Theta_closed: "Theta ∈ GF2.poly_carrier"
  unfolding Theta_def by (intro GF2.poly_add_closed GF2_monom_carrier GF2_poly_const_carrier)

text ‹@{term Theta} has degree exactly @{text 3}.›
lemma degree_Theta: "GF2.degree Theta = 3"
proof (rule antisym)
  show "GF2.degree Theta ≤ 3"
    by (rule GF2.degree_leI[OF Theta_closed]) (simp add: Theta_coeff)
  have "Theta 3 ≠ 0" by (simp add: Theta_coeff)
  moreover have "finite {i. Theta i ≠ 0}" using Theta_closed by (rule GF2.poly_carrier_finite)
  ultimately show "3 ≤ GF2.degree Theta"
    by (simp add: GF2.degree_def) (metis (mono_tags, lifting) Max_ge empty_iff mem_Collect_eq)
qed

text ‹@{term Theta} has no root in @{term "{0, 1::nat}"}: ‹α3 + α + 1› is @{text 1} at both
  @{text 0} and @{text 1} (modulo 2).›
lemma Theta_no_root: "α ∈ {0, 1::nat} ⟹ GF2.eval α Theta ≠ 0"
proof -
  assume a: "α ∈ {0, 1::nat}"
  have sum2: "GF2.poly_add (GF2.monom 1 3) (GF2.monom 1 1) ∈ GF2.poly_carrier"
    by (intro GF2.poly_add_closed GF2_monom_carrier)
  ― ‹Evaluate term by term.›
  have "GF2.eval α Theta
        = gf2_add (GF2.eval α (GF2.poly_add (GF2.monom 1 3) (GF2.monom 1 1))) (GF2.eval α (GF2.poly_const 1))"
    unfolding Theta_def
    using GF2.eval_add[OF sum2 GF2_poly_const_carrier a] .
  also have "GF2.eval α (GF2.poly_add (GF2.monom 1 3) (GF2.monom 1 1))
             = gf2_add (GF2.eval α (GF2.monom 1 3)) (GF2.eval α (GF2.monom 1 1))"
    using GF2.eval_add a by blast
  also have "GF2.eval α (GF2.monom 1 3) = gf2_mult 1 (GF2.rpow α 3)"
    using GF2.eval_monom a by blast
  also have "GF2.eval α (GF2.monom 1 1) = gf2_mult 1 (GF2.rpow α 1)"
    using GF2.eval_monom a by blast
  also have "GF2.eval α (GF2.poly_const 1) = 1"
    using GF2.eval_const a by blast
  finally have ev: "GF2.eval α Theta
        = gf2_add (gf2_add (gf2_mult 1 (GF2.rpow α 3)) (gf2_mult 1 (GF2.rpow α 1))) 1" .
  then have "GF2.eval α Theta = gf2_add (gf2_add (gf2_mult 1 (gf2_mult α (gf2_mult α (gf2_mult α 1))))
                                          (gf2_mult 1 (gf2_mult α 1))) 1"
    unfolding GF2.rpow_def by (simp add: eval_nat_numeral)
  then show "GF2.eval α Theta ≠ 0"
    using gf2_cases[OF a] by (auto simp: gf2_add_def gf2_mult_def)
qed

text ‹Therefore @{term Theta} is irreducible over @{text "GF(2)"}.›
theorem Theta_irreducible: "GF2.poly_irreducible Theta"
  using GF2.degree23_no_root_irreducible[OF Theta_closed] degree_Theta Theta_no_root
  by blast

text ‹∗‹Kronecker.›  The quotient @{text "GF(2)[X]/(Θ)"} is a field with ‹23 = 8› elements.›
theorem GF8_is_field:
  "ideal_in_comm_ring (GF2.poly_pideal Theta) GF2.poly_carrier (GF2.poly_add) (GF2.poly_mult) GF2.poly_zero GF2.poly_one
   ∧ Ideal.maximal_ideal (GF2.poly_pideal Theta) GF2.poly_carrier (GF2.poly_add) (GF2.poly_mult) GF2.poly_zero GF2.poly_one"
  using GF2.poly_quotient_field[OF Theta_irreducible] .

theorem card_GF8:
  "card (Ideal.quotient_set (GF2.poly_pideal Theta) GF2.poly_carrier (GF2.poly_add) GF2.poly_zero) = 8"
proof -
  have pnz: "Theta ≠ GF2.poly_zero"
    using degree_Theta by (metis GF2.degree_zero zero_neq_numeral)
  have "card (Ideal.quotient_set (GF2.poly_pideal Theta) GF2.poly_carrier (GF2.poly_add) GF2.poly_zero)
        = card {0, 1::nat} ^ GF2.degree Theta"
    by (rule GF2.card_poly_quotient[OF _ Theta_closed pnz]) simp
  also have "… = 8" by (simp add: degree_Theta eval_nat_numeral)
  finally show ?thesis .
qed

subsection ‹The nine-element field ‹GF(9)› via Kronecker's construction›

text ‹∗‹Kronecker.›  The quotient @{text "GF(3)[X]/(Ψ)"} is a field with ‹32 = 9› elements.›
theorem GF9_is_field:
  "ideal_in_comm_ring (GF3.poly_pideal Psi) GF3.poly_carrier (GF3.poly_add) (GF3.poly_mult) GF3.poly_zero GF3.poly_one
   ∧ Ideal.maximal_ideal (GF3.poly_pideal Psi) GF3.poly_carrier (GF3.poly_add) (GF3.poly_mult) GF3.poly_zero GF3.poly_one"
  using GF3.poly_quotient_field[OF Psi_irreducible] .

theorem card_GF9:
  "card (Ideal.quotient_set (GF3.poly_pideal Psi) GF3.poly_carrier (GF3.poly_add) GF3.poly_zero) = 9"
proof -
  have pnz: "Psi ≠ GF3.poly_zero"
    using degree_Psi by (metis GF3.degree_zero zero_neq_numeral)
  have "card (Ideal.quotient_set (GF3.poly_pideal Psi) GF3.poly_carrier (GF3.poly_add) GF3.poly_zero)
        = card {0..<3::nat} ^ GF3.degree Psi"
    by (rule GF3.card_poly_quotient[OF _ Psi_closed pnz]) simp
  also have "… = 9" by (simp add: degree_Psi)
  finally show ?thesis .
qed

end