Theory Weierstrass_Sigma_Zeta

section ‹The Weierstra\ss\ σ› and ζ› Functions›
theory Weierstrass_Sigma_Zeta
  imports Eisenstein_Series Elliptic_Functions_Library
begin

(*<*)
lemmas [simp del] = div_add div_diff div_mult_self1 div_mult_self2 div_mult_self3 div_mult_self4
(*>*)

subsection ‹The σ› function›

text ‹
  We use a somewhat odd way to define the Weierstra\ss\ σ› function as an infinite product.
  The product in question converges absolutely, but since at the time of writing this we had no
  theory for absolute convergence of products over infinite sets (only sequences), we pick an
  arbitrary sequence and do the product in that order.

  Now that a library of infinite products indexed by an arbitrary set is available one should
  probably switch to that. Maybe the existing Weierstra\ss\ factorisation theory can be generalised
  to the new more general products. Or maybe we can do this without using Weierstra\ss\ factorisation.
  Not sure what the best approach is.
›

lemma (in weierstrass_product) zero_eq [simp]: "f 0 = 1"
proof -
  have "((λx. 1) has_setprod f 0) I"
    using has_setprod[of 0] by simp
  moreover have "((λx. 1) has_setprod 1) I"
    by simp
  ultimately show "f 0 = 1"
    by (rule has_setprod_unique)
qed

context complex_lattice
begin

sublocale weierstrass_sigma: weierstrass_product "λz. z" "λ_. 2" "Λ*"
proof
  have "inf cofinite (principal Λ*)  at_infinity"
  proof (rule filter_leI)
    fix P assume "eventually P (at_infinity :: complex filter)"
    then obtain R where R: "P x" if "norm x  R" for x
      by (auto simp: eventually_at_infinity)
    have "{x  Λ*. ¬ P x}  Λ  ball 0 R"
      using R by force
    moreover have "finite (Λ  ball 0 R)"
      by (rule bounded_lattice_finite) auto
    ultimately have "finite {x  Λ*. ¬ P x}"
      by (rule finite_subset)
    thus "eventually P (inf cofinite (principal Λ*))"
      unfolding eventually_inf_principal eventually_cofinite by simp
  qed
  thus "filterlim (λz. z) at_infinity (inf cofinite (principal (Λ*)))"
    by (simp add: filterlim_def)
next
  fix r :: real
  assume r: "r > 0"
  have "(λw. 1 / norm w ^ 3) summable_on Λ*"
    by (rule eisenstein_series_norm_summable) auto
  hence "(λz. r ^ 3 * (1 / norm z ^ 3)) summable_on Λ*"
    by (intro summable_on_cmult_right)
  thus "(λz. (r / norm z) ^ Suc 2) summable_on Λ*"
    by (simp add: field_simps)
qed auto

definition weierstrass_sigma :: "complex  complex" where
  "weierstrass_sigma z = z * weierstrass_sigma.f z"

lemma has_field_derivative_weierstrass_sigma_0:
  "(weierstrass_sigma has_field_derivative 1) (at 0)"
  unfolding weierstrass_sigma_def
  by (auto intro!: derivative_eq_intros analytic_derivI[OF weierstrass_sigma.analytic])

lemma deriv_weierstrass_sigma_0 [simp]: "deriv weierstrass_sigma 0 = 1"
  by (rule DERIV_imp_deriv, rule has_field_derivative_weierstrass_sigma_0)

lemma analytic_on_weierstrass_sigma [analytic_intros]:
  assumes "f analytic_on A"
  shows   "(λz. weierstrass_sigma (f z)) analytic_on A"
proof -
  have "weierstrass_sigma  f analytic_on A"
    unfolding weierstrass_sigma_def [abs_def] 
    by (intro analytic_on_compose assms analytic_intros)
  thus ?thesis
    by (simp add: o_def)
qed

lemma holomorphic_on_weierstrass_sigma [holomorphic_intros]:
  assumes "f holomorphic_on A"
  shows   "(λz. weierstrass_sigma (f z)) holomorphic_on A"
proof -
  have "weierstrass_sigma  f holomorphic_on A"
    unfolding weierstrass_sigma_def [abs_def] 
    by (intro holomorphic_on_compose assms holomorphic_intros)
  thus ?thesis
    by (simp add: o_def)
qed

lemma continuous_on_weierstrass_sigma [continuous_intros]:
  assumes "continuous_on A f"
  shows   "continuous_on A (λz. weierstrass_sigma (f z))"
  by (rule continuous_on_compose2[OF _ assms order.refl])
     (auto intro!: holomorphic_on_imp_continuous_on holomorphic_intros)

lemma weierstrass_sigma_altdef:
  "weierstrass_sigma z = 
     z * (wΛ*. (1 - z / w) * exp (z / w + z2 / (2 * w2)))"
  unfolding weierstrass_sigma_def weierstrass_sigma.f_def weierstrass_factor_def
  by (simp add: numeral_2_eq_2 Let_def mult_ac)

text ‹
  The $\sigma$ function is odd:
›
lemma weierstrass_sigma_uminus: "weierstrass_sigma (-z) = -weierstrass_sigma z"
proof -
  have "((λx. weierstrass_factor 2 (z / x)) has_setprod weierstrass_sigma.f z) Λ*"
    by (rule weierstrass_sigma.has_setprod)
  also have "?this  ((λx. weierstrass_factor 2 ((-z) / x)) has_setprod weierstrass_sigma.f z) Λ*"
    unfolding weierstrass_factor_def
    by (rule has_setprod_reindex_bij_witness[of _ "λw. -w" "λw. -w"])
       (auto simp: uminus_in_lattice0_iff)
  finally have "((λx. weierstrass_factor 2 ((-z) / x)) has_setprod weierstrass_sigma.f z) Λ*"
    by (simp add: weierstrass_factor_def)
  moreover have "((λx. weierstrass_factor 2 ((-z) / x)) has_setprod weierstrass_sigma.f (-z)) Λ*"
    by (rule weierstrass_sigma.has_setprod)
  ultimately have "weierstrass_sigma.f (-z) = weierstrass_sigma.f z"
    using has_setprod_unique by blast
  thus ?thesis
    by (simp add: weierstrass_sigma_def)
qed

lemma weierstrass_sigma_eq_0_iff: "weierstrass_sigma z = 0  z  Λ"
  by (simp add: weierstrass_sigma_def lattice_lattice0 weierstrass_sigma.zero)

lemma weierstrass_sigma_eq_0 [simp]: "z  Λ  weierstrass_sigma z = 0"
  by (subst weierstrass_sigma_eq_0_iff) auto

lemma has_zorder_weierstrass_sigma [zorder_intros]:
  "has_zorder weierstrass_sigma z (if z  Λ then 1 else 0)"
proof -
  have 1: "has_zorder (λz. z) z (if z = 0 then 1 else 0)"
    by (rule zorder_intros) auto
  have 2: "has_zorder weierstrass_sigma.f z (if z  Λ* then 1 else 0)"
    using weierstrass_sigma.has_zorder[of z] by auto
  have "has_zorder weierstrass_sigma z ((if z = 0 then 1 else 0) + (if z  Λ* then 1 else 0))"
    unfolding weierstrass_sigma_def using has_zorder_mult[OF 1 2 refl] .
  also have "((if z = 0 then 1 else 0) + (if z  Λ* then 1 else 0)) = (if z  Λ then 1 else 0 :: int)"
    by auto
  finally show ?thesis .
qed

lemma zorder_weierstrass_sigma:
  "zorder weierstrass_sigma z = (if z  Λ then 1 else 0)"
  using has_zorder_weierstrass_sigma[of z] by (simp add: has_zorder_def)


subsection ‹The ζ› function›

text ‹
  The Weierstra\ss\ ζ› function, the logarithmic derivative of the Weierstra\ss\ σ›
  function. Not to be confused with the Riemann ζ› function.
›
definition weierstrass_zeta :: "complex  complex" where
  "weierstrass_zeta z =
     (if z  Λ then 0 else deriv weierstrass_sigma z / weierstrass_sigma z)"

lemma has_field_derivative_weierstrass_sigma [derivative_intros]:
  assumes "(f has_field_derivative f') (at z within A)" "f z  Λ"
  shows   "((λz. weierstrass_sigma (f z)) has_field_derivative
             (f' * weierstrass_sigma (f z) * weierstrass_zeta (f z))) (at z within A)"
proof -
  have deriv: "(weierstrass_sigma has_field_derivative 
                 (weierstrass_sigma z * weierstrass_zeta z)) (at z)"
    if z: "z  Λ" for z
  proof -
    have "(weierstrass_sigma has_field_derivative deriv weierstrass_sigma z) (at z)"
      by (rule analytic_derivI) (auto intro!: analytic_intros)
    also have "deriv weierstrass_sigma z = weierstrass_sigma z * weierstrass_zeta z"
      using z by (simp add: weierstrass_zeta_def weierstrass_sigma_eq_0_iff)
    finally show ?thesis .
  qed

  have "((weierstrass_sigma  f) has_field_derivative 
          (weierstrass_sigma (f z) * weierstrass_zeta (f z) * f')) (at z within A)"
    by (intro DERIV_chain assms deriv)
  thus ?thesis
    by (simp add: o_def mult_ac)
qed

lemma has_log_derivative_weierstrass_sigma [derivative_intros]:
  assumes "(f has_field_derivative f') (at z within A)" "f z  Λ"
  shows   "((λz. weierstrass_sigma (f z)) has_log_derivative
             (f' * weierstrass_zeta (f z))) (at z within A)"
proof (cases "at z within A = bot")
  case False
  thus ?thesis using assms
    by (auto simp: has_log_derivative_def Lim_ident_at weierstrass_sigma_eq_0_iff
             intro!: derivative_eq_intros)
qed auto

lemma has_field_derivative_weierstrass_sigma_aux:
  assumes w: "w  Λ"
  shows "(weierstrass_sigma.f has_field_derivative 
           (weierstrass_zeta w - 1 / w) * weierstrass_sigma.f w) (at w)"
proof -
  write weierstrass_sigma ("σ")
  write weierstrass_zeta ("ζ")
  from w have [simp]: "w  0"
    by auto
  from w have "((λw. σ w / w) has_field_derivative (w * ζ w - 1) * σ w / w ^ 2) (at w)"
    by (auto intro!: derivative_eq_intros simp: field_simps power2_eq_square)
  also have "?this  (weierstrass_sigma.f has_field_derivative 
                         ((ζ w - 1 / w) * weierstrass_sigma.f w)) (at w)"
  proof (rule DERIV_cong_ev)
    have "eventually (λw. w  -{0}) (nhds w)"
      by (intro eventually_nhds_in_open) (use w in auto)
    thus "eventually (λw. σ w / w = weierstrass_sigma.f w) (nhds w)"
      by eventually_elim (auto simp: weierstrass_sigma_def)
  qed (auto simp: weierstrass_sigma_def power2_eq_square field_simps)
  finally show ?thesis .
qed

lemma analytic_on_weierstrass_zeta [analytic_intros]:
  assumes "f analytic_on A" "z. z  A  f z  Λ"
  shows   "(λz. weierstrass_zeta (f z)) analytic_on A"
proof -
  have "(λz. deriv weierstrass_sigma z / weierstrass_sigma z) analytic_on (-Λ)"
    by (intro analytic_intros) (auto simp: weierstrass_sigma_eq_0_iff)
  hence "(λz. deriv weierstrass_sigma z / weierstrass_sigma z) holomorphic_on (-Λ)"
    by (rule analytic_imp_holomorphic)
  also have "?this  weierstrass_zeta holomorphic_on (-Λ)"
    by (rule holomorphic_cong) (auto simp: weierstrass_zeta_def)
  finally have "weierstrass_zeta analytic_on (-Λ)"
    by (subst analytic_on_open) (auto intro: closed_subset_lattice)
  hence "weierstrass_zeta analytic_on (f ` A)"
    by (rule analytic_on_subset) (use assms(2) in auto)
  hence "weierstrass_zeta  f analytic_on A"
    by (intro analytic_on_compose assms(1)) (use assms(2) in auto)
  thus ?thesis
    by (simp add: o_def)
qed

lemma holomorphic_on_weierstrass_zeta [holomorphic_intros]:
  assumes "f holomorphic_on A" "z. z  A  f z  Λ"
  shows   "(λz. weierstrass_zeta (f z)) holomorphic_on A"
proof -
  have "weierstrass_zeta holomorphic_on (f ` A)"
    by (intro analytic_imp_holomorphic analytic_intros) (use assms in auto)
  hence "weierstrass_zeta  f holomorphic_on A"
    by (rule holomorphic_on_compose[OF assms(1)])
  thus ?thesis
    by (simp add: o_def)
qed

lemma meromorphic_on_weierstrass_zeta [meromorphic_intros]:
  assumes "f analytic_on A"
  shows   "(λz. weierstrass_zeta (f z)) meromorphic_on A"
proof -
  have "(λz. deriv weierstrass_sigma z / weierstrass_sigma z) meromorphic_on UNIV"
    by (auto intro!: meromorphic_intros analytic_intros
                     analytic_on_imp_meromorphic_on[of weierstrass_sigma])
  also have "eventually (λz. z  Λ) (cosparse UNIV)"
    by (simp add: eventually_not_in_cosparse lattice_sparse)
  hence "eventually (λz. deriv weierstrass_sigma z / weierstrass_sigma z = 
           weierstrass_zeta z) (cosparse UNIV)"
    by eventually_elim (auto simp: weierstrass_zeta_def)
  hence "(λz. deriv weierstrass_sigma z / weierstrass_sigma z) meromorphic_on UNIV 
         weierstrass_zeta meromorphic_on UNIV"
    by (rule meromorphic_on_cong') auto
  finally have "weierstrass_zeta meromorphic_on UNIV" .
  thus ?thesis
    by (rule meromorphic_on_compose[OF _ assms(1)]) auto
qed

lemma continuous_on_weierstrass_zeta [continuous_intros]:
  assumes "continuous_on A f" "z. z  A  f z  Λ"
  shows   "continuous_on A (λz. weierstrass_zeta (f z))"
  by (rule continuous_on_compose2[OF _ assms(1) order.refl])
     (use assms(2) in auto intro!: holomorphic_on_imp_continuous_on holomorphic_intros)

lemma has_zorder_weierstrass_zeta:
  assumes z: "z  Λ"
  shows   "has_zorder weierstrass_zeta z (-1)"
proof -
  have "has_zorder (λz. deriv weierstrass_sigma z / weierstrass_sigma z) z (-1)"
    by (rule has_zorder_logderiv has_zorder_weierstrass_sigma)+ (use z in auto)
  also have "?this  ?thesis"
  proof (rule has_zorder_cong_ev)
    have "eventually (λz. z  Λ) (at z)"
      using not_islimpt_lattice[of z] by (auto simp: islimpt_iff_eventually)
    thus "F z in at z. deriv weierstrass_sigma z / weierstrass_sigma z = weierstrass_zeta z"
      by eventually_elim (auto simp: weierstrass_zeta_def)
  qed auto
  finally show ?thesis .
qed

lemma uniform_limit_weierstrass_zeta:
  assumes K: "compact K" "K  Λ = {}"
  shows   "uniform_limit K (λX z. wX. 1 / (z - w) + 1 / w + z / w2) 
             (λz. weierstrass_zeta z - 1 / z) (finite_subsets_at_top Λ*)"
proof -
  have "uniform_limit K (λJ z. xJ. (z / x)2 / (z - x))
          (λz. deriv weierstrass_sigma.f z / weierstrass_sigma.f z) (finite_subsets_at_top Λ*)"
    by (rule weierstrass_sigma.uniform_limit_logderiv) (use assms in auto)
  also have "?this  ?thesis"
  proof (intro uniform_limit_cong eventually_finite_subsets_at_top_weakI ballI sum.cong refl)
    fix J z w assume "finite J" "J  Λ*" and z: "z  K" and w: "w  J"
    hence "w  z" "w  0"
      using assms by auto
    thus "(z / w)2 / (z - w) = 1 / (z - w) + 1 / w + z / w2"
      by (simp add: field_simps power2_eq_square)
  next
    fix z assume "z  K"
    hence z: "z  0" "z  Λ" "z  Λ*"
      using K by auto
    have "deriv weierstrass_sigma z = z * deriv weierstrass_sigma.f z + weierstrass_sigma.f z"
      unfolding weierstrass_sigma_def
      by (subst complex_derivative_mult_at) (use assms in auto intro!: analytic_intros)
    also have " / weierstrass_sigma z = 1 / z + deriv weierstrass_sigma.f z / weierstrass_sigma.f z"
      using z by (auto simp: weierstrass_sigma_def field_simps weierstrass_sigma.zero)
    also have "deriv weierstrass_sigma z / weierstrass_sigma z = weierstrass_zeta z"
      using z by (simp add: weierstrass_zeta_def)
    finally show "deriv weierstrass_sigma.f z / weierstrass_sigma.f z = weierstrass_zeta z - 1 / z"
      by (simp add: field_simps)
  qed
  finally show ?thesis .
qed

lemma has_sum_weierstrass_zeta:
  assumes "z  Λ"
  shows   "((λw. 1 / (z - w) + 1 / w + z / w2) has_sum (weierstrass_zeta z - 1 / z)) Λ*"
proof -
  have "uniform_limit {z} (λX z. wX. 1 / (z - w) + 1 / w + z / w2) 
             (λz. weierstrass_zeta z - 1 / z) (finite_subsets_at_top Λ*)"
    by (rule uniform_limit_weierstrass_zeta) (use assms in auto)
  thus ?thesis
    by (simp add: has_sum_def)
qed

text ‹
  The Weierstra\ss\ ζ› function is odd as well:
›
lemma weierstrass_zeta_uminus: "weierstrass_zeta (-z) = -weierstrass_zeta z"
proof (cases "z  Λ")
  case z: False
  have "((λz. weierstrass_sigma z + weierstrass_sigma (-z)) has_field_derivative
          (weierstrass_sigma z * (weierstrass_zeta z + weierstrass_zeta (-z)))) (at z)"
    using z weierstrass_sigma_uminus[of z]
    by (auto intro!: derivative_eq_intros simp: uminus_in_lattice_iff ring_distribs)
  hence "((λz. 0) has_field_derivative 
           (weierstrass_sigma z * (weierstrass_zeta z + weierstrass_zeta (-z)))) (at z)"
    by (simp add: weierstrass_sigma_uminus)
  moreover have "((λz. 0::complex) has_field_derivative 0) (at z)"
    by simp
  ultimately have "weierstrass_sigma z * (weierstrass_zeta z + weierstrass_zeta (-z)) = 0"
    by (rule DERIV_unique)
  thus "weierstrass_zeta (-z) = -weierstrass_zeta z"
    using z by (simp add: weierstrass_sigma_eq_0_iff add_eq_0_iff)
qed (auto simp: weierstrass_zeta_def uminus_in_lattice_iff)

text ‹
  The derivative of ζ› is -℘›:
›
theorem has_field_derivative_weierstrass_zeta:
  assumes z: "z  Λ"
  shows   "(weierstrass_zeta has_field_derivative (- z)) (at z)"
proof -
  define f where "f = (λz. weierstrass_zeta z - 1 / z)"
  have "open (-Λ)" "z  -Λ"
    using closed_lattice z by auto
  then obtain R where R: "R > 0" "cball z R  -Λ"
    using open_contains_cball_eq by blast

  have *: "F X in finite_subsets_at_top Λ*.
             continuous_on (cball z R) (λz. wX. 1 / (z - w) + 1 / w + z / w2) 
             (zball z R. ((λz. wX. 1 / (z - w) + 1 / w + z / w2) has_field_derivative
                                 (wX. 1 / w2 - 1 / (z - w)2)) (at z))"
  proof (intro eventually_finite_subsets_at_top_weakI conjI ballI, goal_cases)
    case (1 X)
    thus ?case using R by (auto intro!: continuous_intros simp: lattice0_def)
  next
    case (2 X u)
    have "u  X" "0  X"
      using R 2 by force+
    hence "((λz. wX. 1 / (z - w) + 1 / w + z / w2) has_field_derivative
                (wX. w ^ 2 / w ^ 4 - 1 / (u - w)2)) (at u)"
      by (auto intro!: derivative_eq_intros sum.cong simp flip: power2_eq_square)
    also have "(wX. w ^ 2 / w ^ 4 - 1 / (u - w)2) = (wX. 1 / w ^ 2 - 1 / (u - w)2)"
      by (intro sum.cong) (auto simp: field_simps power_numeral_reduce)
    finally show ?case .
  qed

  obtain g where g:
     "(f has_field_derivative g w) (at w) 
        ((λwa. 1 / wa2 - 1 / (w - wa)2) has_sum g w) Λ*" 
   if "w  ball z R" for w
    unfolding has_sum_def f_def
    by (rule has_complex_derivative_uniform_limit[OF * uniform_limit_weierstrass_zeta])
       (use R in auto)
  have "(f has_field_derivative g z) (at z)"
    using g[of z] R > 0 by auto
  hence "((λw. f w + 1 / w) has_field_derivative (g z - 1 / z ^ 2)) (at z)"
    using z by (auto intro!: derivative_eq_intros simp: power2_eq_square)
  hence deriv: "(weierstrass_zeta has_field_derivative (g z - 1 / z ^ 2)) (at z)"
    by (simp add: f_def)

  have "((λw. 1 / w2 - 1 / (z - w)2) has_sum g z) Λ*"
    using g[of z] R > 0 by (auto simp: has_sum_iff)
  hence "((λw. 1 / (z - w)2 - 1 / w2) has_sum (-g z)) Λ*"
    by (subst has_sum_uminus [symmetric]) auto
  moreover have "((λw. 1 / (z - w)2 - 1 / w2) has_sum weierstrass_fun_aux z) Λ*"
    by (rule weierstrass_fun_aux_has_sum) (use z in auto)
  ultimately have eq: "weierstrass_fun_aux z = -g z"
    using has_sum_unique by blast

  from deriv show "(weierstrass_zeta has_field_derivative (- z)) (at z)"
    using z by (simp add: weierstrass_fun_def eq)
qed

lemma has_field_derivative_weierstrass_zeta' [derivative_intros]:
  assumes "(f has_field_derivative f') (at z within A)" "f z  Λ"
  shows   "((λz. weierstrass_zeta (f z)) has_field_derivative (-f' *  (f z))) (at z within A)"
proof -
  have "((weierstrass_zeta  f) has_field_derivative ((- (f z)) * f')) (at z within A)"
    by (rule DERIV_chain[OF has_field_derivative_weierstrass_zeta[OF assms(2)] assms(1)])
  thus ?thesis
    by (simp add: o_def mult_ac)
qed

lemma deriv_weierstrass_zeta:
  assumes "z  Λ"
  shows   "deriv weierstrass_zeta z = - z"
  by (rule DERIV_imp_deriv) (use assms in auto intro!: derivative_eq_intros)


subsection ‹Series expansions›

text ‹
  Lastly, we look at the series expansion of $\zeta(z)$ and $\sigma(z)$ at $z = 0$.

  The expansion of $\zeta$ has the following form:
  \[\zeta(z) = \frac{1}{z} - \sum_{k\geq 3} G_{k+1} z^k\]
›

definition fps_weierstrass_zeta :: "complex fps" where
  "fps_weierstrass_zeta = Abs_fps (λk. if k < 3 then 0 else -eisenstein_series (Suc k))"

definition fls_weierstrass_zeta :: "complex fls" where
  "fls_weierstrass_zeta = fls_X_intpow (-1) + fps_to_fls fps_weierstrass_zeta"

lemma fps_deriv_weierstrass_zeta: "fps_deriv fps_weierstrass_zeta = -fps_weierstrass"
proof (rule fps_ext)
  fix n
  show "fps_nth (fps_deriv fps_weierstrass_zeta) n = fps_nth (-fps_weierstrass) n"
    by (cases "n = 1") (auto simp: fps_weierstrass_zeta_def fps_weierstrass_def not_less)
qed

lemma fls_deriv_weierstrass_zeta: "fls_deriv fls_weierstrass_zeta = -fls_weierstrass"
  by (simp add: fls_weierstrass_zeta_def fls_weierstrass_def fls_deriv_fps_to_fls
                fls_deriv_shift fps_deriv_weierstrass_zeta)

lemma has_laurent_expansion_weierstrass_zeta [laurent_expansion_intros]:
  "weierstrass_zeta has_laurent_expansion fls_weierstrass_zeta"
proof -
  have [simp]: "fls_nth fls_weierstrass_zeta 0 = 0"
    by (simp add: fls_weierstrass_zeta_def fps_weierstrass_zeta_def)
  have "weierstrass_zeta meromorphic_on {0}"
    by (auto intro!: meromorphic_intros)
  then obtain F where F: "weierstrass_zeta has_laurent_expansion F"
    by (auto simp: meromorphic_on_def)

  have "((λz. -weierstrass_zeta z)  (λz. -z)) has_laurent_expansion (fls_compose_fps (-F) (-fps_X))"
    by (intro has_laurent_expansion_compose F has_laurent_expansion_fps 
              laurent_expansion_intros fps_expansion_intros) auto
  hence "weierstrass_zeta has_laurent_expansion (fls_compose_fps (-F) (-fps_X))"
    by (auto simp: o_def weierstrass_zeta_uminus)
  with F have "fls_compose_fps (-F) (-fps_X) = F"
    using has_laurent_expansion_unique by blast
  hence "fls_nth (fls_compose_fps (-F) (-fps_X)) 0 = fls_nth F 0"
    by (rule arg_cong)
  hence [simp]: "fls_nth F 0 = 0"
    using fls_nth_fls_compose_fps_linear[of "-1" F 0] by (simp flip: fps_const_neg)

  have "deriv weierstrass_zeta has_laurent_expansion fls_deriv F"
    by (intro laurent_expansion_intros F)
  also have "?this  (λz. - z) has_laurent_expansion fls_deriv F"
  proof (rule has_laurent_expansion_cong)
    have "eventually (λz. z  Λ) (at 0)"
      by (rule eventually_not_in_lattice_at)
    thus "F z in at 0. deriv weierstrass_zeta z = - z"
      by eventually_elim (auto simp: deriv_weierstrass_zeta)
  qed auto
  finally have "(λz. - z) has_laurent_expansion fls_deriv F" .
  moreover have "(λz. - z) has_laurent_expansion (-fls_weierstrass)"
    by (intro laurent_expansion_intros)
  ultimately have "fls_deriv F = fls_deriv fls_weierstrass_zeta"
    using has_laurent_expansion_unique unfolding fls_deriv_weierstrass_zeta by blast
  hence "F = fls_weierstrass_zeta"
    by (simp add: fls_deriv_eq_iff)
  thus ?thesis
    using F by simp
qed

lemma residue_weierstrass_zeta: "residue weierstrass_zeta 0 = 1"
  using has_laurent_expansion_residue_0[OF has_laurent_expansion_weierstrass_zeta]
  by (simp add: fls_weierstrass_zeta_def)



text ‹
  The expansion for $\sigma$ has the following form:
  \[\sigma(z) = z \exp\left( \sum_{k\geq 3} \frac{G_k}{k} z^k \right)\]
›

definition fps_weierstrass_sigma_aux1 :: "complex fps" where
  "fps_weierstrass_sigma_aux1 = 
     Abs_fps (λk. if k  2 then 0 else eisenstein_series k / of_nat k)"

definition fps_weierstrass_sigma_aux2 :: "complex fps" where
  "fps_weierstrass_sigma_aux2 = fps_compose (fps_exp (-1)) fps_weierstrass_sigma_aux1"

definition fps_weierstrass_sigma :: "complex fps" where
  "fps_weierstrass_sigma = fps_X * fps_weierstrass_sigma_aux2"

lemma fps_nth_0_weierstrass_sigma_aux2 [simp]:
  "fps_nth fps_weierstrass_sigma_aux2 0 = 1"
  by (simp add: fps_weierstrass_sigma_aux2_def)

lemma fps_nth_0_weierstrass_sigma [simp]:
  "fps_nth fps_weierstrass_sigma 0 = 0"
  by (simp add: fps_weierstrass_sigma_def)

lemma fps_weierstrass_sigma_aux2_nonzero [simp]: "fps_weierstrass_sigma_aux2  0"
  by (auto simp: fps_weierstrass_sigma_aux2_def fps_compose_eq_0_iff fps_weierstrass_sigma_aux1_def)

lemma fps_weierstrass_sigma_nonzero [simp]: "fps_weierstrass_sigma  0"
  by (auto simp: fps_weierstrass_sigma_def)

lemma fps_nth_1_weierstrass_sigma [simp]:
  "fps_nth fps_weierstrass_sigma (Suc 0) = 1"
  by (simp add: fps_weierstrass_sigma_def)

lemma subdegree_fps_weierstrass_sigma_aux2 [simp]:
  "subdegree fps_weierstrass_sigma_aux2 = 0"
  by (rule subdegreeI) auto

lemma subdegree_fps_weierstrass_sigma [simp]:
  "subdegree fps_weierstrass_sigma = 1"
  by (rule subdegreeI) auto


lemma has_fps_expansion_weierstrass_sigma_aux [fps_expansion_intros]:
  "weierstrass_sigma.f has_fps_expansion fps_weierstrass_sigma_aux2"
proof -
  write weierstrass_sigma ("σ")
  write weierstrass_zeta ("ζ")

  have "weierstrass_sigma.f analytic_on {0}"
    by (auto intro!: analytic_intros)
  then obtain F where F: "weierstrass_sigma.f has_fps_expansion F"
    using analytic_at_imp_has_fps_expansion_0 by blast
  have F': "weierstrass_sigma.f has_laurent_expansion (fps_to_fls F)"
    unfolding weierstrass_sigma_def by (intro has_laurent_expansion_fps fps_expansion_intros F)

  define H1 where "H1 = fps_weierstrass_sigma_aux1"
  define H2 where "H2 = fps_weierstrass_sigma_aux2"
  obtain c where c: "F = fps_const c * H2"
  proof (rule fps_logderiv_unique)
    have "fps_nth H2 0  fps_nth 0 0"
      by (auto simp: H2_def fps_weierstrass_sigma_aux2_def)
    thus "H2  0"
      by blast
  next      
    have "deriv weierstrass_sigma.f has_laurent_expansion fls_deriv (fps_to_fls F)"
      by (intro laurent_expansion_intros F')
    also have "?this  (λz. (ζ z - 1 / z) * weierstrass_sigma.f z) has_laurent_expansion 
                            fls_deriv (fps_to_fls F)"
    proof (intro has_laurent_expansion_cong)
      show "eventually (λw. deriv weierstrass_sigma.f w = (ζ w - 1 / w) * weierstrass_sigma.f w) (at 0)"
        using eventually_not_in_lattice_at
      proof eventually_elim
        case (elim w)
        thus ?case by (intro DERIV_imp_deriv has_field_derivative_weierstrass_sigma_aux)
      qed
    qed auto
    finally have "(λz. (ζ z - 1 / z) * weierstrass_sigma.f z) has_laurent_expansion fls_deriv (fps_to_fls F)" .
    moreover have "(λz. (ζ z - 1 / z) * weierstrass_sigma.f z) has_laurent_expansion
                     ((fls_weierstrass_zeta - 1 / fls_X) * fps_to_fls F)"
      by (intro laurent_expansion_intros F')
    ultimately have "fls_deriv (fps_to_fls F) = (fls_weierstrass_zeta - 1 / fls_X) * fps_to_fls F"
      using has_laurent_expansion_unique by blast
    also have " = fps_to_fls (F * fps_weierstrass_zeta)"
      by (simp add: fls_weierstrass_zeta_def fls_times_fps_to_fls)
    also have "fls_deriv (fps_to_fls F) = fps_to_fls (fps_deriv F)"
      by (simp add: fls_deriv_fps_to_fls)
    finally show "fps_deriv F = fps_weierstrass_zeta * F"
      by (simp add: mult_ac)
  next
    have [simp]: "fps_nth H1 0 = 0"
      by (simp add: H1_def fps_weierstrass_sigma_aux1_def)
    have "fps_deriv H1 = -fps_weierstrass_zeta"
    proof (rule fps_ext)
      show "fps_nth (fps_deriv H1) n = fps_nth (-fps_weierstrass_zeta) n" for n
        by (cases "n = 2")
           (auto simp: H1_def fps_weierstrass_zeta_def fps_weierstrass_sigma_aux1_def 
                 simp del: of_nat_Suc intro!: fps_ext)
    qed
    thus "fps_deriv H2 = fps_weierstrass_zeta * H2"
      by (simp add: H2_def fps_weierstrass_sigma_aux2_def fps_compose_deriv fps_compose_uminus
               flip: fps_const_neg H1_def)
  qed

  have "c = fps_nth (fps_const c * H2) 0"
    by (auto simp: H2_def fps_weierstrass_sigma_aux2_def)
  also have " = fps_nth F 0"
    by (simp add: c)
  also have " = 1"
    using has_fps_expansion_imp_0_eq_fps_nth_0[OF F] by simp
  finally have [simp]: "c = 1" .

  from F and c show ?thesis
    by (simp add: H2_def)
qed

theorem has_fps_expansion_weierstrass_sigma [fps_expansion_intros]:
  "weierstrass_sigma has_fps_expansion fps_weierstrass_sigma"
  unfolding weierstrass_sigma_def fps_weierstrass_sigma_def
  by (intro fps_expansion_intros)


subsection ‹Quasiperiodicity and Legendre's relation›

definition weierstrass_eta :: "complex  complex" where
  "weierstrass_eta ω = 
     weierstrass_zeta (ω1 / 2 + ω) - weierstrass_zeta (ω1 / 2)"

text ‹
  The Weierstra\ss\ $\eta$ function gives the shifts induced in $\zeta$ by a lattice point.
›
theorem weierstrass_zeta_plus_lattice:
  assumes ω: "ω  Λ" and z: "z  Λ"
  shows   "weierstrass_zeta (z + ω) = weierstrass_zeta z + weierstrass_eta ω"
proof -
  define f where "f = (λz. weierstrass_zeta (z + ω) - weierstrass_zeta z)"
  have "f constant_on (-Λ)"
  proof (rule has_field_derivative_0_imp_constant_on)
    show "(f has_field_derivative 0) (at z)" if z: "z  -Λ" for z
      unfolding f_def using ω z
      by (auto intro!: derivative_eq_intros weierstrass_fun.lattice_cong 
               simp: rel_def uminus_in_lattice_iff)
  next
    have "connected (UNIV - Λ)"
      by (rule connected_open_diff_countable) auto
    also have "UNIV - Λ = -Λ"
      by auto
    finally show "connected (-Λ)" .
  qed (use closed_lattice in auto)
  then obtain c where c: "f z = c" if "z  Λ" for z
    by (auto simp: constant_on_def)
  have "f z = f (ω1 / 2)"
    using z by (simp add: c)
  thus ?thesis
    by (simp add: f_def weierstrass_eta_def algebra_simps)
qed


text ‹
  The Weierstra\ss\ $\eta$ function is a group homomorphism from termΛ to ℂ›.
  Hence the values $\eta_1 = \eta(\omega_1)$ and $\eta_2 = \eta(\omega_2)$ are of particular
  interest since they generate all the remaining ones.
›
lemma weierstrass_eta_0 [simp]: "weierstrass_eta 0 = 0"
  by (simp add: weierstrass_eta_def)

lemma weierstrass_eta_add:
  assumes "ω  Λ" "ω'  Λ"
  shows   "weierstrass_eta (ω + ω') = weierstrass_eta ω + weierstrass_eta ω'"
  using weierstrass_zeta_plus_lattice[of ω "ω1 / 2"]
        weierstrass_zeta_plus_lattice[of ω' "ω1 / 2 + ω"]
        weierstrass_zeta_plus_lattice[of "ω + ω'" "ω1 / 2"] assms
  by (simp add: algebra_simps)

lemma weierstrass_eta_uminus:
  assumes "ω  Λ"
  shows   "weierstrass_eta (-ω) = -weierstrass_eta ω"
  using assms uminus_in_lattice weierstrass_eta_0 weierstrass_eta_add
  by (metis diff_minus_eq_add right_minus_eq)

lemma weierstrass_eta_diff:
  assumes "ω  Λ" "ω'  Λ"
  shows   "weierstrass_eta (ω - ω') = weierstrass_eta ω - weierstrass_eta ω'"
  using weierstrass_eta_add[of ω "-ω'"] assms
  by (simp add: weierstrass_eta_uminus uminus_in_lattice_iff)

lemma weierstrass_eta_of_nat_times_lattice:
  assumes "ω  Λ"
  shows   "weierstrass_eta (of_nat n * ω) = of_nat n * weierstrass_eta ω"
  by (induction n)
     (simp_all add: weierstrass_eta_add assms ring_distribs weierstrass_eta_add lattice_intros)

lemma weierstrass_eta_of_int_times_lattice:
  assumes "ω  Λ"
  shows   "weierstrass_eta (of_int n * ω) = of_int n * weierstrass_eta ω"
proof (cases "n  0")
  case True
  define m where "m = nat n"
  have n_eq: "n = int m"
    using True by (auto simp: m_def)
  show ?thesis
    by (simp add: n_eq weierstrass_eta_of_nat_times_lattice assms)
next
  case False
  define m where "m = nat (-n)"
  have n_eq: "n = -int m"
    using False by (auto simp: m_def)
  show ?thesis
    by (simp add: n_eq weierstrass_eta_of_nat_times_lattice weierstrass_eta_uminus assms lattice_intros)
qed

lemma weierstrass_eta_of_ω12_coords:
  "weierstrass_eta (of_ω12_coords (of_int m, of_int n)) = 
     of_int m * weierstrass_eta ω1 + of_int n * weierstrass_eta ω2"
  by (simp add: of_ω12_coords_def weierstrass_eta_add 
                weierstrass_eta_of_int_times_lattice lattice_intros)

lemma weierstrass_eta_conv_zeta:
  assumes "ω  Λ" "ω / 2  Λ"
  shows   "weierstrass_eta ω = 2 * weierstrass_zeta (ω / 2)"
  using weierstrass_zeta_plus_lattice[of ω "-ω/2"] assms
  by (simp add: uminus_in_lattice_iff weierstrass_zeta_uminus)

lemma weierstrass_eta1_conv_zeta: "weierstrass_eta ω1 = 2 * weierstrass_zeta (ω1 / 2)"
  and weierstrass_eta2_conv_zeta: "weierstrass_eta ω2 = 2 * weierstrass_zeta (ω2 / 2)"
  by (rule weierstrass_eta_conv_zeta; simp; fail)+


text ‹
  Legendre's relation links $\eta_1$ and $\eta_2$. This is proven in a straightforward way by
  integrating $\zeta$ along a parallelogram-shaped contour not containing any lattice points.
  For convenience, we choose the one that starts at $\frac{1}{2}(\omega_1 + \omega_2)$.
›
theorem legendre_relation:
  "ω2 * weierstrass_eta ω1 - ω1 * weierstrass_eta ω2 = of_real (2 * pi * sgn (Im (ω2 / ω1))) * 𝗂"
proof -                          
  write weierstrass_zeta ("ζ")
  write weierstrass_eta ("η")
  define z0 where "z0 = (-(ω1 + ω2) / 2)"
  define P where "P = period_parallelogram z0"
  define γ where "γ = parallelogram_path z0 ω1 ω2"

  have closure_P: "Λ  closure P = {0}"
  proof -
    have "closure P = of_ω12_coords ` (λ(a,b). (a-1/2, b-1/2)) ` (cbox (0, 0) (1, 1))"
      unfolding P_def closure_period_parallelogram image_image z0_def
      by (simp add: of_ω12_coords_def case_prod_unfold diff_divide_distrib algebra_simps)
    also have "Λ   = {0}" (is "?lhs = ?rhs")
    proof (intro equalityI subsetI)
      fix z assume "z  ?lhs"
      then obtain a b 
        where ab: "z = of_ω12_coords (a - 1 / 2, b - 1 / 2)" "a  {0..1}" "b  {0..1}" 
                  "a - 1 / 2  " "b - 1 / 2  "
        by (auto simp: of_ω12_coords_in_lattice_iff mem_box Basis_pair_def)
      then obtain m n where "a - 1 / 2 = of_int m" "b - 1 / 2 = of_int n"
        by (elim Ints_cases)
      hence mn: "a = of_int m + 1 / 2" "b = of_int n + 1 / 2"
        by auto
      from ab(2,3) have "m = 0  n = 0"
        by (auto simp: mn)
      with ab(1) show "z  {0}"
        by (auto simp: mn)
    next
      fix z assume "z  {0::complex}"
      hence "z  Λ" "z = of_ω12_coords ((λ(a,b). (a - 1/2, b - 1/2)) (1/2, 1/2))"
           "(1/2, 1/2)  cbox (0, 0) (1 :: real, 1 :: real)"
        by auto
      thus "z  ?lhs"
        by blast
    qed
    finally show ?thesis .
  qed

  have γ: "Λ  path_image γ = {}"
  proof -
    have "path_image γ = of_ω12_coords ` (λ(a,b). (a-1/2, b-1/2)) ` (cbox (0, 0) (1, 1) - box (0, 0) (1, 1))"
      unfolding γ_def path_image_parallelogram_path' image_image z0_def
      by (simp add: of_ω12_coords_def case_prod_unfold diff_divide_distrib algebra_simps)
    also have "Λ   = {}"
      by (auto simp: of_ω12_coords_in_lattice_iff mem_box Basis_pair_def)
    finally show ?thesis .
  qed

  have "compact (closure P)"
    unfolding P_def by auto
  then obtain R where R: "closure P  ball 0 R"
    using compact_imp_bounded bounded_subset_ballD by blast
  define A :: "complex set" where "A = ball 0 R"
  have A: "closure P  A"
    using R by (simp add: A_def)
  have fin: "finite (Λ  A)"
    by (rule bounded_lattice_finite) (auto simp: A_def)

  have "0 = z0 + of_ω12_coords (1/2, 1/2)" "(1/2 :: real, 1/2::real)  box (0, 0) (1, 1)"
    by (auto simp: z0_def of_ω12_coords_def field_simps mem_box Basis_pair_def)
  hence "0  interior P"
    unfolding P_def interior_period_parallelogram by blast

  define s where "s = complex_of_real (sgn (Im (ω2 / ω1)))"
  have s: "s  {-1, 1}"
    using fundpair by (auto simp: s_def sgn_if fundpair_def complex_is_Real_iff)
  
  have "contour_integral γ ζ = of_real (2 * pi) * 𝗂 * (zΛA. winding_number γ z * residue ζ z)"
  proof (rule Residue_theorem)
    show "open A" "connected A"
      by (auto simp: A_def)
  next
    show "ζ holomorphic_on A - Λ  A"
      by (auto intro!: holomorphic_intros)
  next
    show "path_image γ  A - Λ  A"
      using A γ path_image_parallelogram_subset_closure[of z0] 
      by (auto simp: γ_def P_def)
  next
    show "finite (Λ  A)"
      by fact
  next
    show "z. z  A  winding_number γ z = 0"
      using A unfolding γ_def P_def by (auto intro!: winding_number_parallelogram_outside)
  qed (auto simp: γ_def)
  also have "(pΛA. winding_number γ p * residue ζ p) =
             (p{0::complex}. s)"
  proof (intro sum.mono_neutral_cong_right ballI, goal_cases)
    case (3 z)
    have "z  closure P"
      using closure_P using 3 by auto
    hence "winding_number γ z = 0"
      unfolding γ_def P_def using winding_number_parallelogram_outside by blast
    thus ?case
      by simp
  next
    case (4 z)
    with 0  interior P have "winding_number γ z = s"
      unfolding γ_def by (subst winding_number_parallelogram_inside) (auto simp: s_def P_def)
    thus ?case using residue_weierstrass_zeta 4
      by simp
  next
    show "{0}  Λ  A"
      using 0  interior P interior_subset[of P] closure_subset[of P] A by auto
  qed (use fin in auto)
  also have " = s"
    by simp
  also have "contour_integral γ ζ =
               contour_integral (linepath z0 (z0 + ω1)) (λx. ζ x - ζ (x + ω2)) -
               contour_integral (linepath z0 (z0 + ω2)) (λx. ζ x - ζ (x + ω1))" unfolding γ_def
    by (rule contour_integral_parallelogram_path')
       (use γ in auto intro!: continuous_intros simp: γ_def)
  also have "contour_integral (linepath z0 (z0 + ω1)) (λx. ζ x - ζ (x + ω2)) =
             contour_integral (linepath z0 (z0 + ω1)) (λx. -η ω2)"
  proof (intro contour_integral_cong refl)
    fix z assume "z  path_image (linepath z0 (z0 + ω1))"
    then obtain u where u: "u  {0..1}" "z = linepath z0 (z0 + ω1) u"
      unfolding path_image_def by blast
    have "z = of_ω12_coords (u - 1 / 2, -1 / 2)"
      by (auto simp: of_ω12_coords_def u(2) linepath_def scaleR_conv_of_real field_simps z0_def)
    also have "  Λ"
      by (auto simp: of_ω12_coords_in_lattice_iff)
    finally show "ζ z - ζ (z + ω2) = -η ω2"
      by (subst weierstrass_zeta_plus_lattice) auto
  qed
  also have " = -η ω2 * ω1"
    by simp
  also have "contour_integral (linepath z0 (z0 + ω2)) (λx. ζ x - ζ (x + ω1)) =
             contour_integral (linepath z0 (z0 + ω2)) (λx. -η ω1)"
  proof (intro contour_integral_cong refl)
    fix z assume "z  path_image (linepath z0 (z0 + ω2))"
    then obtain u where u: "u  {0..1}" "z = linepath z0 (z0 + ω2) u"
      unfolding path_image_def by blast
    have "z = of_ω12_coords (-1 / 2, u - 1 / 2)"
      by (auto simp: of_ω12_coords_def u(2) linepath_def scaleR_conv_of_real field_simps z0_def)
    also have "  Λ"
      by (auto simp: of_ω12_coords_in_lattice_iff)
    finally show "ζ z - ζ (z + ω1) = -η ω1"
      by (subst weierstrass_zeta_plus_lattice) auto
  qed
  also have " = -η ω1 * ω2"
    by simp
  finally show ?thesis
    by (auto simp: mult_ac s_def)
qed


text ‹
  The $\sigma$ function satisfies a similar quasiperiodicity. We first derive the version that
  assumes $\frac{\omega}{2} \notin \Lambda$ and then use this to prove the general version.
›
lemma weierstrass_sigma_plus_lattice_weak:
  assumes ω: "ω  Λ" "ω / 2  Λ"
  shows "weierstrass_sigma (z + ω) =
           -exp (weierstrass_eta ω * (z + ω / 2)) * weierstrass_sigma z"
proof (cases "z  Λ")
  case True
  thus ?thesis
    using ω by simp
next
  case z: False
  write weierstrass_sigma ("σ")
  write weierstrass_eta ("η")
  write weierstrass_zeta ("ζ")
  define g where
    "g = (λz. σ (z + ω) / (exp (η ω * z) * σ z))"

  have "g constant_on (-Λ)"
  proof (rule has_field_derivative_0_imp_constant_on)
    fix z assume z: "z  -Λ"
    have "(g has_field_derivative 
            (σ z * σ (z + ω) * exp (η ω * z) * (ζ (z + ω) - ζ z - η ω) / (exp (η ω * z) * σ z) ^ 2)) (at z)"
      using z ω by (auto simp: g_def weierstrass_sigma_eq_0_iff simp: algebra_simps power2_eq_square
                         intro!: derivative_eq_intros)
    also have "ζ (z + ω) - ζ z - η ω = 0"
      by (subst weierstrass_zeta_plus_lattice) (use ω z in auto)
    finally show "(g has_field_derivative 0) (at z)"
      by simp
  next
    have "connected (UNIV - Λ)"
      by (rule connected_open_diff_countable) auto
    also have "UNIV - Λ = -Λ" by auto
    finally show "connected (-Λ)" .
  qed (use closed_lattice in auto)

  then obtain c where c: "g z = c" if "z  Λ" for z
    by (auto simp: constant_on_def)
  have "c = g (-ω/2)"
    using ω by (simp add: c uminus_in_lattice_iff)
  also have " = -exp (η ω * ω / 2)" using ω 
    by (simp add: g_def weierstrass_sigma_uminus weierstrass_sigma_eq_0_iff exp_minus field_simps)
  finally have c_eq: "c = -exp (η ω * ω / 2)" .

  show ?thesis
    using c[of z] z
    by (simp add: g_def c_eq field_simps weierstrass_sigma_eq_0_iff flip: exp_add)
qed

theorem weierstrass_sigma_plus_lattice:
  assumes ω: "ω  Λ"
  defines "ε  (if ω/2  Λ then 1 else -1)"
  shows "weierstrass_sigma (z + ω) =
           ε * exp (weierstrass_eta ω * (z + ω / 2)) * weierstrass_sigma z"
proof -
  write weierstrass_sigma ("σ")
  write weierstrass_eta ("η")

  have 1: "σ (z + of_nat n * ω) = 
          (if even n then 1 else -1) * exp (of_nat n * η ω * (z + of_nat n * ω / 2)) * σ z"
    if ω: "ω  Λ" "ω / 2  Λ" for n ω z
  proof (induction n)
    case (Suc n)
    have "σ (z + of_nat (Suc n) * ω) = σ (z + of_nat n * ω + ω)"
      by (simp add: algebra_simps)
    also have " = -exp (η ω * (z + of_nat n * ω + ω / 2)) * σ (z + of_nat n * ω)"
      by (subst weierstrass_sigma_plus_lattice_weak) (use ω in auto)
    also have " = (if even (Suc n) then 1 else -1) * 
                       exp (η ω * (z + of_nat n * ω + ω / 2) + of_nat n * η ω * (z + of_nat n * ω / 2)) * σ z"
      unfolding exp_add by (subst Suc.IH) (auto simp: algebra_simps)
    also have "η ω * (z + of_nat n * ω + ω / 2) + of_nat n * η ω * (z + of_nat n * ω / 2) =
               of_nat (Suc n) * η ω * (z + of_nat (Suc n) * ω / 2)"
      by (simp add: field_simps)
    finally show ?case .
  qed auto

  have 2: "σ (z + of_int n * ω) = 
            (if even n then 1 else -1) * exp (of_int n * η ω * (z + of_int n * ω / 2)) * σ z"
    if ω: "ω  Λ" "ω / 2  Λ" for n ω z
  proof (cases "n  0")
    case True
    define m where "m = nat n"
    have n_eq: "n = int m"
      using True by (auto simp: m_def)
    show ?thesis
      using 1[of ω z m] ω by (simp add: n_eq)
  next
    case False
    define m where "m = nat (-n)"
    have n_eq: "n = -int m"
      using False by (auto simp: m_def)
    show ?thesis
      using 1[of "-ω" z m] ω by (simp add: n_eq uminus_in_lattice_iff weierstrass_eta_uminus)
  qed

  from assms obtain m n where ω: "ω = of_ω12_coords (of_int m, of_int n)"
    by (auto simp: lattice_def elim!: Ints_cases)
  define c where "c = of_int (m * n) / 2 * (ω2 * η ω1 - ω1 * η ω2)"
  define s where "s = (if Im (ω2 / ω1) > 0 then 1 else -1 :: int)"

  have "σ (z + ω) = σ (z + of_int m * ω1 + of_int n * ω2)"
    by (simp add: ω of_ω12_coords_def algebra_simps)
  also have " = (if even n then 1 else -1) * 
                    exp (of_int n * η ω2 * (z + of_int m * ω1 + of_int n * ω2 / 2)) *
                    σ (z + of_int m * ω1)"
    by (subst 2) auto
  also have " = (if even n = even m then 1 else -1) * 
                    exp (of_int n * η ω2 * (z + of_int m * ω1 + of_int n * ω2 / 2) + 
                         of_int m * η ω1 * (z + of_int m * ω1 / 2)) * σ z"
    unfolding exp_add by (subst 2) (auto simp: mult_ac)
  also have "of_int n * η ω2 * (z + of_int m * ω1 + of_int n * ω2 / 2) + 
               of_int m * η ω1 * (z + of_int m * ω1 / 2) =
             η ω * (z + ω / 2) - c" 
    unfolding ω by (subst weierstrass_eta_of_ω12_coords) 
                   (simp_all add: of_ω12_coords_def field_simps c_def)
  also have "exp  = exp (η ω * (z + ω / 2)) / exp c"
    by (simp add: exp_diff)
  also have "c = of_int (m * n) * sgn (Im (ω2 / ω1)) * 𝗂 * pi"
    by (simp add: c_def legendre_relation)
  also have " = of_int (m * n * s) * 𝗂 * pi"
    using ratio_not_in_Reals by (auto simp: s_def sgn_if ratio_def complex_is_Real_iff)
  also have "exp  = cis (of_int (m * n * s) * pi)"
    by (simp add: exp_eq_polar)
  also have " = cis pi powi (m * n * s)"
    by (subst cis_power_int) auto
  also have " = (-1) powi (m * n * s)"
    by simp
  also have " = (-1) powi (m * n)"
    by (auto simp: s_def power_int_minus field_simps)
  also have "(if even n = even m then 1 else -1) * (exp (η ω * (z + ω / 2)) / ) * σ z =
             (if even m  even n then 1 else -1) * exp (η ω * (z + ω / 2)) * σ z"
    by auto
  also have "even m  even n  of_ω12_coords (of_int m / 2, of_int n / 2)  Λ"
    unfolding of_ω12_coords_in_lattice_iff
    using of_int_div_of_int_in_Ints_iff[of m 2] of_int_div_of_int_in_Ints_iff[of n 2]
    by auto
  also have "of_ω12_coords (of_int m / 2, of_int n / 2) = ω / 2"
    by (auto simp: ω of_ω12_coords_def field_simps)
  finally show ?thesis
    by (simp add: ε_def)
qed

end


subsection ‹Expressing elliptic functions in terms of $\sigma$›

subsubsection ‹Multisets modulo lattice points›

text ‹
  In this section, we build some infrastructure to count the number of occurrences of a point
  in a multiset modulo the lattice.
›

context complex_lattice
begin

definition count_rel :: "complex multiset  complex  nat" where
  "count_rel A z = size {# w ∈# A. rel w z #}"

lemma count_rel_eq_0_iff: "count_rel A z = 0  (w∈#A. ¬rel w z)"
  by (auto simp: count_rel_def)

lemma count_rel_pos_iff: "count_rel A z > 0  (w∈#A. rel w z)"
  using count_rel_eq_0_iff[of A z] by auto

lemma count_rel_cong: "rel z z'  count_rel A z = count_rel A z'"
  unfolding count_rel_def using rel_trans rel_sym by metis

lemma count_rel_to_fund_parallelogram:
  "count_rel (image_mset to_fund_parallelogram A) z = count_rel A z"
  by (simp add: count_rel_def filter_mset_image_mset)

lemma count_rel_empty [simp]: "count_rel {#} z = 0"
  by (simp_all add: count_rel_def)

lemma count_rel_add_mset [simp]:
  "count_rel (add_mset w A) z = (if rel w z then 1 else 0) + count_rel A z"
  by (auto simp: count_rel_def)

lemma count_rel_replicate_mset [simp]:
  "count_rel (replicate_mset n w) z = (if rel w z then n else 0)"
  by (induction n) (auto simp: count_rel_def)

lemma count_rel_add [simp]: "count_rel (A + B) z = count_rel A z + count_rel B z"
  by (simp_all add: count_rel_def)

lemma count_rel_sum [simp]: "count_rel (xA. f x) z = (xA. count_rel (f x) z)"
  by (induction A rule: infinite_finite_induct) auto

lemma count_rel_diff_subset:
  assumes "B ⊆# A"
  shows   "count_rel (A - B) z = count_rel A z - count_rel B z"
proof -
  define C where "C = A - B"
  have A_eq: "A = B + C"
    using assms unfolding C_def by (metis subset_mset.add_diff_inverse)
  have "count_rel A z - count_rel B z = count_rel C z"
    by (simp add: A_eq)
  thus ?thesis
    by (simp add: C_def)
qed

lemma sum_count_rel_eq_size:
  assumes "finite X" "x. x ∈# A  yX. rel x y" "X  period_parallelogram 0"
  shows   "(zX. count_rel A z) = size A"
proof -
  define A' where "A' = image_mset to_fund_parallelogram A"
  have "(zX. count_rel A z) = (zX. count_rel A' z)"
    by (simp add: A'_def count_rel_to_fund_parallelogram)
  also have " = (zset_mset A'. count_rel A' z)"
  proof (intro sum.mono_neutral_right ballI)
    show "set_mset A'  X"
      using assms to_fund_parallelogram_unique by (auto simp: A'_def)
  next
    fix x assume "x  X - set_mset A'"
    thus "count_rel A' x = 0"
      unfolding A'_def count_rel_to_fund_parallelogram
      using assms to_fund_parallelogram_unique[of _ x]
      by (fastforce simp: count_rel_def)
  qed fact+
  also have " = (zset_mset A'. count A' z)"
  proof (intro sum.cong refl)
    fix z assume z: "z ∈# A'"
    hence "{# w ∈# A'. rel w z #} = {# w ∈# A'. w = z #}"
      by (intro filter_mset_cong) (auto simp: A'_def)
    thus "count_rel A' z = count A' z"
      by (simp add: count_rel_def count_conv_size_mset)
  qed
  also have " = size A'"
    by (simp add: size_multiset_overloaded_eq)
  also have " = size A"
    by (simp add: A'_def)
  finally show ?thesis .
qed


subsubsection ‹Products and quotients of $\sigma$›

text ‹
  Next, we introduce a ``multiple \sigma'' function 
  \[\sigma_{A,B}(z) = \frac{\prod_{a\in A} \sigma(z-a)}{\prod_{b\in B} \sigma(z-b)}\]
  where $A$ and $B$ are multisets of complex numbers. Typically we will have $|A|=|B|$, since
  then $\sigma_{A,B}$ is elliptic.
›

definition weierstrass_multi_sigma :: "complex multiset  complex multiset  complex  complex"
  where "weierstrass_multi_sigma A B = 
           (λz. (w∈#A. weierstrass_sigma (z - w)) / (w∈#B. weierstrass_sigma (z - w)))"

lemma weierstrass_multi_sigma_eq_0_iff:
  "weierstrass_multi_sigma A B z = 0  (w∈#(A+B). rel w z)"
  by (force simp: weierstrass_multi_sigma_def weierstrass_sigma_eq_0_iff rel_def 
                  diff_in_lattice_commute)

lemma analytic_on_weierstrass_multi_sigma [analytic_intros]:
  assumes "f analytic_on X" "z w. z  X  w ∈# B  ¬rel (f z) w"
  shows   "(λz. weierstrass_multi_sigma A B (f z)) analytic_on X"
  using assms unfolding weierstrass_multi_sigma_def
  by (auto intro!: analytic_intros simp: weierstrass_sigma_eq_0_iff rel_def)

lemma holomorphic_on_weierstrass_multi_sigma [holomorphic_intros]:
  assumes "f holomorphic_on X" "z w. z  X  w ∈# B  ¬rel (f z) w"
  shows   "(λz. weierstrass_multi_sigma A B (f z)) holomorphic_on X"
  using assms unfolding weierstrass_multi_sigma_def
  by (auto intro!: holomorphic_intros simp: weierstrass_sigma_eq_0_iff rel_def)

lemma meromorphic_on_weierstrass_multi_sigma [meromorphic_intros]:
  assumes "f analytic_on X"
  shows   "(λz. weierstrass_multi_sigma A B (f z)) meromorphic_on X"
  using assms unfolding weierstrass_multi_sigma_def
  by (intro meromorphic_intros) (auto intro!: analytic_on_imp_meromorphic_on analytic_intros)

text ‹
  Obviously, the zero order of any point is the number of its occurrences in $A$ minus those
  in $B$, counted modulo lattice points.

  Note that this does allow points to be present in both $A$ and $B$. If that happens, they do
  cancel except for a removable singularity. This could be fixed using either a more careful
  definition or the constremove_sings operator, but it is not worth the effort.
›
lemma has_zorder_weierstrass_multi_sigma:
  "has_zorder (weierstrass_multi_sigma A B) z (int (count_rel A z) - int (count_rel B z))"
proof -
  write weierstrass_sigma ("σ")
  define g where "g = (λX z. w∈#X. σ (z - w))"
  have *: "has_zorder (g X) z (count_rel X z)" for X z
  proof -
    have "has_zorder (g X) z (w∈#X. if rel w z then 1 else 0)"
      unfolding g_def
    proof (rule zorder_intros)
      fix w assume w: "w ∈# X"
      have "has_zorder σ (z - w) (if z - w  Λ then 1 else 0)"
        by (rule has_zorder_weierstrass_sigma)
      thus "has_zorder (λz. σ (z - w)) z (if rel w z then 1 else 0)"
        by (auto simp: rel_def diff_in_lattice_commute has_zorder_shift' algebra_simps)
    qed
    also have " = count_rel X z"
      by (induction X) auto
    finally show ?thesis .
  qed

  have "has_zorder (λz. g A z / g B z) z 
          (int (count_rel A z) - int (count_rel B z))"
    using * * by (rule has_zorder_divide) auto
  thus ?thesis
    by (simp add: weierstrass_multi_sigma_def g_def)
qed

lemma nicely_meromorphic_weierstrass_multi_sigma [meromorphic_intros]:
  assumes "a b. a ∈# A  b ∈# B  ¬rel a b"
  defines "f  weierstrass_multi_sigma A B"
  shows "f nicely_meromorphic_on X"
  unfolding nicely_meromorphic_on_def
proof (intro conjI ballI)
  fix z assume "z  X"
  show "is_pole f z  f z = 0  f z f z"
  proof (cases "b∈#B. rel z b")
    case False
    hence "f z f z"
      by (auto intro!: analytic_at_imp_isCont isContD analytic_intros simp: f_def)
    thus ?thesis by blast
  next
    case True
    with assms(1) have not_in_A: "a∈#A. ¬rel z a"
      using rel_trans rel_sym by blast
    define n where "n = int (count_rel B z)"
    have "has_zorder f z (int (count_rel A z) - int (count_rel B z))"
      unfolding f_def by (rule has_zorder_weierstrass_multi_sigma)
    also have "int (count_rel A z) = 0"
      using not_in_A by (simp add: count_rel_eq_0_iff rel_sym)
    finally have "has_zorder f z (-n)"
      by (simp add: n_def)
    moreover have "n > 0"
      using True by (auto simp: n_def count_rel_pos_iff rel_sym)
    ultimately have "is_pole f z"
      by (simp add: has_zorder_imp_is_pole_iff)
    moreover have "f z = 0"
      using True by (auto simp: f_def weierstrass_multi_sigma_eq_0_iff rel_sym)
    ultimately show ?thesis
      by blast
  qed
qed (auto intro!: meromorphic_intros simp: f_def)

end


locale elliptic_multiple_sigma = complex_lattice +
  fixes A B :: "complex multiset"
  assumes same_size: "size A = size B"
  assumes sum_eq: "sum_mset A = sum_mset B"
begin

sublocale elliptic_function ω1 ω2 "weierstrass_multi_sigma A B"
proof
  show "weierstrass_multi_sigma A B meromorphic_on UNIV" unfolding weierstrass_multi_sigma_def
    by (intro meromorphic_intros) (auto intro!: analytic_on_imp_meromorphic_on analytic_intros)
next
  write weierstrass_sigma ("σ")
  write weierstrass_eta ("η")
  have *: "weierstrass_multi_sigma A B (z + ω) = weierstrass_multi_sigma A B z" 
    if ω: "ω  Λ" "ω / 2  Λ" for z ω
  proof -
    define n where "n = size A"
    define c where "c = (-1) ^ n * exp (of_nat n * (η ω * (z + ω / 2)))"
    have [simp]: "c  0"
      by (auto simp: c_def)

    have *: "(w∈#X. σ (z + ω - w)) = c / exp (η ω * sum_mset X) * (w∈#X. σ (z - w))"
      if "size X = n" for X
    proof -
      have "(w∈#X. σ (z + ω - w)) = (w∈#X. -exp (η ω * (z - w + ω / 2)) * σ (z - w))"
      proof (intro arg_cong[of _ _ prod_mset] image_mset_cong)
        fix w assume w: "w ∈# X"
        have "σ (z + ω - w) = σ (z - w + ω)"
          by (simp add: algebra_simps)
        also have " = -exp (η ω * (z - w + ω / 2)) * σ (z - w)"
          by (rule weierstrass_sigma_plus_lattice_weak) (use ω in auto)
        finally show "σ (z + ω - w) = " .
      qed
      also have " = (w∈#X. (-1) * exp (η ω * (z - w + ω / 2))) * (w∈#X. σ (z - w))"
        by (subst prod_mset.distrib) auto
      also have " = (-1) ^ n * (w∈#X. exp (η ω * (z - w + ω / 2))) * (w∈#X. σ (z - w))"
        by (subst prod_mset.distrib) (auto simp: size X = n)
      also have "(w∈#X. exp (η ω * (z - w + ω / 2))) = exp (w∈#X. η ω * (z - w + ω / 2))"
        by (simp add: exp_sum_mset')
      also have "(w∈#X. η ω * (z - w + ω / 2)) = 
                   of_nat n * (η ω * (z + ω / 2)) - η ω * sum_mset X"
        by (simp add: size X = n ring_distribs sum_mset.distrib sum_mset_subtractf 
                      sum_mset_distrib_left)
      finally show "(w∈#X. σ (z + ω - w)) = c / exp (η ω * sum_mset X) * (w∈#X. σ (z - w))"
        by (simp add: exp_diff c_def size X = n)
    qed

    show "weierstrass_multi_sigma A B (z + ω) = weierstrass_multi_sigma A B z"
      using same_size by (simp add: weierstrass_multi_sigma_def * n_def exp_diff sum_eq)
  qed

  show "weierstrass_multi_sigma A B (z + ω1) = weierstrass_multi_sigma A B z"
       "weierstrass_multi_sigma A B (z + ω2) = weierstrass_multi_sigma A B z" for z
    using *[of ω1] *[of ω2] by simp_all
qed

end

locale nicely_elliptic_multiple_sigma = elliptic_multiple_sigma +
  assumes disjoint: "a b. a ∈# A  b ∈# B  ¬rel a b"
begin

sublocale nicely_elliptic_function ω1 ω2 "weierstrass_multi_sigma A B"
  by standard (use disjoint in auto intro!: meromorphic_intros)

end



subsubsection ‹Writing concrete elliptic functions in terms of $\sigma$›

lemma (in elliptic_function) in_terms_of_sigma_explicit_aux:
  assumes "z. zorder f z = int (count_rel A z) - int (count_rel B z)"
  assumes "sum_mset A = sum_mset B"
  assumes "elliptic_order f > 0"
  shows "size A = size B"
proof -
  define P where "P = period_parallelogram 0"
  define P' where "P' = to_fund_parallelogram ` (set_mset (A + B))"

  have "((λz. zorder f z) has_sum 0) P"
    using has_sum_zorder_0[of 0] elliptic_order f > 0 by (simp add: P_def)
  also have "?this  ((λz. zorder f z) has_sum 0) P'"
  proof (rule has_sum_cong_neutral)
    fix z assume "z  P - P'"
    hence "count_rel A z = 0" "count_rel B z = 0"
      using to_fund_parallelogram_unique by (auto simp: P_def P'_def count_rel_eq_0_iff)
    thus "zorder f z = 0"
      by (simp add: assms(1))
  qed (auto simp: P'_def P_def)
  also have "  ((zP'. zorder f z) = 0)"
    by (subst has_sum_finite_iff) (auto simp: P'_def)
  also have "(zP'. zorder f z) = int (zP'. count_rel A z) - int (zP'. count_rel B z)"
    by (simp add: assms(1) sum_subtractf)
  also have " = int (size A) - int (size B)"
    by (subst (1 2) sum_count_rel_eq_size) (auto simp: P'_def)
  finally show "size A = size B"
    by simp
qed

text ‹
  Consider an elliptic function $f$.
  Given a complete set of representatives of the zeros $A$ and the poles $B$
  (with the right multiplicities) such that $\sum A = \sum B$, we can write $f$ in terms of 
  the Weierstra\ss\ σ› function as
  \[ f(z) = c \frac{\prod_{a\in A} \sigma(z-a)}{\prod_{b\in B} \sigma(z-b)} \]
  where $c$ is a constant. Note that $A$ and $B$ are multisets here.

  This is useful when trying to establish a relationship between σ› and a particular elliptic
  function with known zeros and poles. Later, we will apply this to a function of the form
  $\wp(z) - \wp(z_0)$.
›
lemma (in elliptic_function) in_terms_of_sigma_explicit:
  assumes "z. zorder f z = int (count_rel A z) - int (count_rel B z)"
  assumes "sum_mset A = sum_mset B"
  assumes "elliptic_order f > 0"
  obtains c where "c  0" "z. f z = c * weierstrass_multi_sigma A B z"
proof -
  define h where "h = (λz. f z / weierstrass_multi_sigma A B z)"
  have "size A = size B"
    by (rule in_terms_of_sigma_explicit_aux) (use assms in auto)
  interpret construct: elliptic_multiple_sigma ω1 ω2 A B
    by standard (use size A = size B sum_mset A = sum_mset B in auto)
  interpret h: elliptic_function ω1 ω2 h
    unfolding h_def by (intro elliptic_function_intros)

  have "¬is_pole h z" for z
  proof -
    have "has_zorder h z 0"
      unfolding h_def
    proof (rule has_zorder_divide)
      show "has_zorder f z (zorder f z)"
        using avoid'[of 0] elliptic_order f > 0
        by (auto simp: has_zorder_def intro!: meromorphic_on_subset[OF meromorphic])
    next
      show "has_zorder (weierstrass_multi_sigma A B) z (zorder f z)"
        using assms(1)[of z] has_zorder_weierstrass_multi_sigma[of A B z] by simp
    qed auto
    thus "¬is_pole h z"
      using has_zorder_imp_is_pole_iff by blast
  qed
  hence "elliptic_order h = 0"
    using h.elliptic_order_eq_0_iff_no_poles by metis
  then obtain c where c: "z. h z = c"
    using h.elliptic_order_eq_0_iff_const_cosparse by blast

  have "z. w∈#(A+B). ¬rel z w"
    by (intro eventually_ball_finite ballI eventually_not_rel_cosparse) auto
  hence ev_nz: "z. weierstrass_multi_sigma A B z  0"
    by eventually_elim (auto simp: weierstrass_multi_sigma_eq_0_iff rel_sym)

  have "c  0"
  proof -
    have "z. f z  0"
      using avoid[of 0] elliptic_order f > 0 by simp
    with ev_nz c have "(z::complex). c  0"
      by eventually_elim (auto simp: h_def)
    thus ?thesis
      by simp
  qed

  show ?thesis
  proof (rule that)
    have "z. w∈#(A+B). ¬rel z w"
      by (intro eventually_ball_finite ballI eventually_not_rel_cosparse) auto
    show "z. f z = c * weierstrass_multi_sigma A B z"
      using ev_nz c by eventually_elim (auto simp: h_def)
  qed fact+
qed

text ‹
  For nicely elliptic functions we can slightly strengthen the conclusion from
  ``equal except at singularities'' to ``equal everywhere''.
›
lemma (in nicely_elliptic_function) in_terms_of_sigma_explicit':
  assumes "z. zorder f z = int (count_rel A z) - int (count_rel B z)"
  assumes disjoint: "a b. a ∈# A  b ∈# B  ¬rel a b"
  assumes "sum_mset A = sum_mset B"
  assumes "elliptic_order f > 0"
  obtains c where "c  0" "z. f z = c * weierstrass_multi_sigma A B z"
proof -
  obtain c where c: "c  0" "z. f z = c * weierstrass_multi_sigma A B z"
    by (rule in_terms_of_sigma_explicit[of A B]) (use assms in auto)
  have "size A = size B"
    by (rule in_terms_of_sigma_explicit_aux) (use assms in auto)
  interpret multi_sigma: nicely_elliptic_multiple_sigma ω1 ω2 A B
    by standard (use assms size A = size B in auto)
  show ?thesis
  proof (rule that[of c])
    fix z :: complex
    show "f z = c * weierstrass_multi_sigma A B z" using c(2)
      by (rule nicely_meromorphic_on_cosparse_eq_imp_eq) (auto intro!: meromorphic_intros)
  qed fact+
qed

text ‹
  We now look at two concrete examples.

  First, we consider the function $f(z) = \wp(z) - \wp(z_0)$ for some $z_0\notin\Lambda$.
  This function has a double pole at every lattice point and a single zero at lattice equivalents
  of $\pm z_0$, except if $2z_0\in\Lambda$, in which case it has a double zero at lattice
  equivalents of $z_0$.

  In either case, the representatives $A = \{z_0, -z_0\}$ for the zeroes and $B = \{0, 0\}$ 
  for the poles satisfy the requirements of the theorem above and we obtain
  \[ \wp(z) - \wp(z_0) = c \frac{\sigma(z+z_0)\sigma(z-z_0)}{\sigma(z)^2}\]
  for some constant $c$.

  It remains to determine what the constant $c$ is. To do this, we inspect the leading coefficient
  of the Laurent series expansion of both sides at $z = 0$. More concretely, we multiply both sides
  with $z^2$ and take the limit as $z\to 0$. This way, we easily find that 
  $c = -\frac{1}{\sigma(z_0)^2}$ and therefore:
  \[\wp(z) = \wp(z_0) - \frac{\sigma(z+z_0)\sigma(z-z_0)}{\sigma(z)^2 \sigma(z_0)^2}\]
›
theorem (in complex_lattice) weierstrass_fun_conv_sigma_half_period:
  assumes z0: "z0  Λ" and z: "z  Λ"
  defines "σ  weierstrass_sigma"
  shows " z =  z0 - σ (z + z0) * σ (z - z0) / (σ z * σ z0)2"
proof -
  define g where "g = (λz.  z -  z0)"
  interpret g: weierstrass_fun_minus_const ω1 ω2 z0 g
    by unfold_locales (use z0 in auto simp: g_def)
  write weierstrass_sigma ("σ")

  obtain c where c: "z. g z = c * σ (z - z0) * σ (z + z0) / σ z ^ 2"
  proof (rule g.affine.in_terms_of_sigma_explicit)
    show "elliptic_order g > 0"
      by (auto simp: g.order_eq)
  next
    show "zorder g z = int (count_rel {#z0, -z0#} z) - int (count_rel {#0, 0#} z)" for z
    proof -
      consider "z  Λ" | "rel z z0  rel z (-z0)" | "z  Λ" "¬rel z z0  ¬rel z (-z0)"
        by blast
      thus ?thesis
      proof cases
        assume z: "z  Λ"
        have "¬rel z z0"
          using z0 z rel_lattice_trans_left by blast
        with z z0 show ?thesis
          by (subst g.zorder_pole_eq)
             (auto simp: is_pole_weierstrass_fun_iff zorder_weierstrass_fun_pole 
                         rel_def uminus_in_lattice_iff diff_in_lattice_commute)
      next
        assume z: "rel z z0  rel z (-z0)"
        from z have "z  Λ"
          using z0 rel_lattice_trans_left uminus_in_lattice_iff by blast
        have "2 * z0  Λ  rel z z0  rel z (-z0)"
        proof
          assume "2 * z0  Λ"
          hence "rel z z0  rel z (-z0)"
            using add_in_lattice[of "z - z0" "2 * z0"] diff_in_lattice[of "z + z0" "2 * z0"]
            by (auto simp: add.commute rel_def)
          thus "rel z z0  rel z (-z0)"
            using z by auto
        next
          assume "rel z z0  rel z (-z0)"
          hence "rel z0 (-z0)"
            using rel_trans rel_sym by blast
          thus "2 * z0  Λ"
            by (auto simp: rel_def)
        qed
        with z z  Λ show ?thesis
          by (auto simp: g.zorder_zero_eq rel_def diff_in_lattice_commute uminus_in_lattice_iff)
      qed (auto simp: g.zorder_zero_eq' rel_def uminus_in_lattice_iff diff_in_lattice_commute)
    qed
  qed (auto simp: weierstrass_multi_sigma_def power2_eq_square mult_ac)

  have c_eq: "c = -1 / σ z0 ^ 2"
  proof -
    note [tendsto_intros] =
      isContD[of _ "λz. σ (z - z0)"] isContD[of _ "λz. σ (z + z0)"] isContD[of _ weierstrass_sigma.f]
    have "(λz. z ^ 2 *  z - z ^ 2 *  z0 - c * σ (z - z0) * σ (z + z0) / weierstrass_sigma.f z ^ 2)
             0 (1 - 0 ^ 2 *  z0 - c * σ (0 - z0) * σ (0 + z0) / weierstrass_sigma.f 0 ^ 2)"
    proof (intro tendsto_intros analytic_at_imp_isCont analytic_intros)
      have *: "(λz. z2 *  z) has_laurent_expansion (fls_X ^ 2 * fls_weierstrass)"
        by (intro laurent_expansion_intros)
      have "fls_subdegree (fls_X2 * fls_weierstrass) = 0"
        by (subst fls_subdegree_mult) (auto simp: fls_subdegree_weierstrass)
      hence "(λz. z2 *  z) 0 fls_nth (fls_X2 * fls_weierstrass) 0"
        using has_laurent_expansion_imp_tendsto_0[OF *] by simp
      also have "fls_nth (fls_X2 * fls_weierstrass) 0 = 1"
        by (simp add: fls_X_power_conv_shift_1 fls_X_intpow_times_conv_shift 
                      fls_weierstrass_def fps_weierstrass_def)
      finally show "(λz. z2 *  z) 0 1" .
    qed auto
    also have "?this  (λz::complex. 0) 0 (1 + c * σ z0 ^ 2)"
    proof (rule filterlim_cong)
      have "eventually (λz. g z = c * σ (z - z0) * σ (z + z0) / σ z ^ 2) (at 0)"
        using c by (auto simp: eventually_cosparse_open_eq)
      thus "F x in at 0. x2 *  x - x2 *  z0 - c * σ (x - z0) * σ (x + z0) /
                                      (weierstrass_sigma.f x)2 = 0"
        using eventually_neq_at_within[of 0]
      proof eventually_elim
        case (elim z)
        thus ?case
          by (auto simp: g_def weierstrass_sigma_def[of z] field_simps power2_eq_square)
      qed
    qed (auto simp: weierstrass_sigma_uminus power2_eq_square mult_ac)
    finally have "0 = 1 + c * σ z0 ^ 2"
      by (simp add: tendsto_const_iff)
    thus "c = -1 / σ z0 ^ 2"
      using z0 by (auto simp: field_simps weierstrass_sigma_eq_0_iff add_eq_0_iff)
  qed

  have "g z = c * σ (z - z0) * σ (z + z0) / (σ z)2"
    using c
  proof (rule analytic_on_continuation)
    show "g analytic_on (-Λ)"
      by (auto simp: g_def intro!: analytic_intros)
    show "(λz. c * σ (z - z0) * σ (z + z0) / σ z ^ 2) analytic_on (-Λ)"
      by (auto intro!: analytic_intros simp: weierstrass_sigma_eq_0_iff)
  qed (use z in auto)
  thus ?thesis
    by (simp add: g_def c_eq field_simps σ_def)
qed

text ‹
  Next, we analogously derive a formula for $\wp'(z)$ interms of $\sigma$, namely:
  \[\wp'(z) = \frac{2}{\sigma(z)^3} \prod_{i=1,2,3} \frac{\sigma(z-h_i)}{\sigma(h_i)}\]
  where $h_1 = \frac{1}{2}\omega_1$, $h_2 = \frac{1}{2}\omega_2$, and 
  $h_3 = -\frac{1}{2}(\omega_1+\omega_2)$.
›
theorem (in complex_lattice) weierstrass_fun_deriv_conv_sigma_half_period:
  defines "σ  weierstrass_sigma"
  shows "℘' z = 2 / σ z ^ 3 * (w[ω1/2, ω2/2, -(ω1+ω2)/2]. σ (z-w) / σ w)"
proof -
  write weierstrass_sigma ("σ")
  define h1 where "h1 = of_ω12_coords (1/2, 0)"
  define h2 where "h2 = of_ω12_coords (0, 1/2)"
  define h3 where "h3 = of_ω12_coords (-1/2, -1/2)"
  have "h3  Λ"
    using half_periods_notin_lattice(3) uminus_in_lattice_iff[of "(ω1+ω2)/2"]
    by (auto simp: h3_def add_divide_distrib diff_divide_distrib of_ω12_coords_def minus_diff_commute)
  hence h123: "h{h1,h2,h3}. h  Λ  2 * h  Λ"
    by (auto simp: h1_def h2_def h3_def lattice_intros diff_divide_distrib of_ω12_coords_def)
  have h123_unique: "¬rel h1 h2  ¬rel h1 h3  ¬rel h2 h3"
    unfolding h1_def h2_def h3_def rel_def of_ω12_coords.diff [symmetric] of_ω12_coords_in_lattice_iff
    by auto
  have zorder_h123: "h{h1,h2,h3}. zorder ℘' h = 1"
    using h123 by (auto intro!: zorder_weierstrass_fun_deriv_zero)
  have [simp]: "zorder ℘' (to_fund_parallelogram z) = zorder ℘' z" for z
    by (rule weierstrass_fun_deriv.zorder.lattice_cong) auto
  have zorder_0: "zorder ℘' z = 0" if "z  Λ" "h{h1,h2,h3}. ¬rel z h" for z
  proof -
    have "rel ((ω1 + ω2) / 2) h3"
      by (auto simp: rel_def h3_def of_ω12_coords_def diff_divide_distrib add_divide_distrib)
    hence *: "2 * z  Λ"
      using that rel_half_period[of z] rel_trans[of z "(ω1+ω2)/2" h3]
      by (auto simp: h1_def h2_def h3_def of_ω12_coords_def)
    show ?thesis
    by (rule zorder_eq_0I) 
       (use that * in auto intro!: analytic_intros simp: weierstrass_fun_deriv_eq_0_iff)
  qed

  obtain c where c: "z. ℘' z = c * σ (z - h1) * σ (z - h2) * σ (z - h3) / σ z ^ 3"
  proof (rule weierstrass_fun_deriv.in_terms_of_sigma_explicit')
    show "elliptic_order ℘' > 0"
      by simp
  next
    show "zorder ℘' z = int (count_rel {#h1, h2, h3#} z) - int (count_rel {#0, 0, 0#} z)" for z
    proof -
      define z' where "z' = to_fund_parallelogram z"
      have rel_iff: "rel w z  to_fund_parallelogram w = z'" for w
        by (auto simp: z'_def simp flip: to_fund_parallelogram_eq_iff)
      have z_in_lattice_iff: "z  Λ  z' = 0"
        by (auto simp: z'_def)
      have *: "zorder ℘' z = zorder ℘' z'"
        by (simp add: z'_def)
      show ?thesis
        using h123 h123_unique zorder_h123 zorder_0[of z]
        by (auto simp: rel_iff zorder_weierstrass_fun_deriv_pole rel_sym z_in_lattice_iff *)
    qed
  next
    fix a b :: complex
    assume "a ∈# {#h1, h2, h3#}" "b ∈# {#0, 0, 0#}"
    thus "¬rel a b"
      using h123 by (auto simp: rel_def)
  qed (auto simp: h1_def h2_def h3_def of_ω12_coords_def weierstrass_multi_sigma_def 
                  mult_ac power_numeral_reduce)

  have c_eq: "c = 2 / (σ h1 * σ h2 * σ h3)"
  proof -
    note [tendsto_intros] =
      isContD[of _ "λz. σ (z - w)" for w] isContD[of _ weierstrass_sigma.f]
    have "(λz. z ^ 3 * ℘' z - c * σ (z - h1) * σ (z - h2) * σ (z - h3) / weierstrass_sigma.f z ^ 3)
             0 (-2 - c * σ (0 - h1) * σ (0 - h2) * σ (0 - h3) / weierstrass_sigma.f 0 ^ 3)"
    proof (intro tendsto_intros analytic_at_imp_isCont analytic_intros)
      have *: "(λz. z ^ 3 * ℘' z) has_laurent_expansion (fls_X ^ 3 * fls_deriv fls_weierstrass)"
        by (intro laurent_expansion_intros)
      have "fls_subdegree (fls_X ^ 3 * fls_deriv fls_weierstrass) = 0"
      proof (subst fls_subdegree_mult)
        have "fls_nth (fls_deriv fls_weierstrass) (-3)  fls_nth 0 (-3)"
          by (auto simp: fls_weierstrass_def)
        thus "fls_deriv fls_weierstrass  0"
          by metis
      qed (auto simp: fls_subdegree_weierstrass fls_subdegree_deriv)
      hence "(λz. z ^ 3 * ℘' z) 0 fls_nth (fls_X ^ 3 * fls_deriv fls_weierstrass) 0"
        using has_laurent_expansion_imp_tendsto_0[OF *] by simp
      also have "fls_nth (fls_X ^ 3 * fls_deriv fls_weierstrass) 0 = -2"
        by (simp add: fls_X_power_conv_shift_1 fls_X_intpow_times_conv_shift 
                      fls_weierstrass_def fps_weierstrass_def)
      finally show "(λz. z ^ 3 * ℘' z) 0 -2" .
    qed auto
    also have "?this  (λz::complex. 0) 0 (-2 + c * σ h1 * σ h2 * σ h3)"
    proof (rule filterlim_cong)
      have "eventually (λz. ℘' z = c * σ (z - h1) * σ (z - h2) * σ (z - h3) / σ z ^ 3) (at 0)"
        using c by (auto simp: eventually_cosparse_open_eq)
      thus "F x in at 0. x ^ 3 * ℘' x - c * σ (x - h1) * σ (x - h2) * σ (x - h3) /
                                      weierstrass_sigma.f x ^ 3 = 0"
        using eventually_neq_at_within[of 0]
      proof eventually_elim
        case (elim z)
        thus ?case
          by (auto simp: weierstrass_sigma_def[of z] field_simps power2_eq_square)
      qed
    qed (auto simp: weierstrass_sigma_uminus mult_ac)
    finally have "0 = -2 + c * σ h1 * σ h2 * σ h3"
      by (simp add: tendsto_const_iff)
    thus "c = 2 / (σ h1 * σ h2 * σ h3)"
      using h123 by (auto simp: field_simps weierstrass_sigma_eq_0_iff add_eq_0_iff)
  qed

  show ?thesis using c[of z]
    by (simp add: c_eq field_simps σ_def h1_def h2_def h3_def of_ω12_coords_def minus_diff_commute)
qed


subsubsection ‹Generic representation theorem in terms of $\sigma$›

text ‹
  Lastly, we prove a generic theorem for an arbitrary elliptic function, showing that it can
  be written in the form $c \cdot \sigma_{A,B}$ for an appropriate constant $c$ and
  multisets $A$ and $B$.

  The construction is fairly straightforward: $A$ consists of all zeros in the fundamental
  parallelogram (with appropriate multiplicities) and $B$ of all the poles. However, this will
  violate the condition that $\sum A = \sum B$, so we pick some arbitrary zero $a\in A$ and shift
  it by a lattice point in order to make things work out.
›
theorem (in elliptic_function) in_terms_of_sigma:
  obtains c A B 
  where "size A = elliptic_order f" "z. z ∈# A  isolated_zero f z"
        "size B = elliptic_order f" "z. z ∈# B  is_pole f z"
        "sum_mset A = sum_mset B"
        "z. f z = c * weierstrass_multi_sigma A B z"
proof (cases "elliptic_order f = 0")
  case True
  then obtain c where c: "z. f z = c"
    using elliptic_order_eq_0_iff_const_cosparse by blast
  show ?thesis
    using that[of "{#}" "{#}" c] c True by (auto simp: weierstrass_multi_sigma_def)
next
  case False
  define P where "P = period_parallelogram 0"
  note fin = finite_poles_in_parallelogram[of 0, folded P_def]
             finite_zeros_in_parallelogram[of 0, folded P_def]
  define A where "A = (z | z  P  isolated_zero f z. replicate_mset (nat (zorder f z)) z)"
  define B where "B = (z | z  P  is_pole f z. replicate_mset (nat (-zorder f z)) z)"
  have "size A = elliptic_order f"
    using zeros_eq_elliptic_order[of 0] by (simp add: A_def P_def)
  have "size B = elliptic_order f"
    using poles_eq_elliptic_order[of 0] by (simp add: B_def P_def)
  have "A  {#}"
    using False size A = _ by auto
  then obtain a where a: "a ∈# A"
    by blast
  define d where "d = sum_mset A - sum_mset B"
  have "d  Λ"
  proof -
    have "d = (z | z  P  isolated_zero f z. of_nat (nat (zorder f z)) * z) -
              (z | z  P  is_pole f z. of_nat (nat (- zorder f z)) * z)"
      by (simp add: d_def A_def B_def sum_mset_sum)
    also have "(z | z  P  isolated_zero f z. of_nat (nat (zorder f z)) * z) =
               (z | z  P  isolated_zero f z. of_int (zorder f z) * z)"
      using False by (intro sum.cong) (auto simp: isolated_zero_iff_zorder_pos)
    also have "(z | z  P  is_pole f z. of_nat (nat (- zorder f z)) * z) =
               (z | z  P  is_pole f z. -of_int (zorder f z) * z)"
      using False by (intro sum.cong) (auto simp: is_pole_iff_zorder_neg) 
    also have "(z | z  P  isolated_zero f z. of_int (zorder f z) * z) -
               (z | z  P  is_pole f z. -of_int (zorder f z) * z) =
               (z{zP. isolated_zero f z}{zP. is_pole f z}. of_int (zorder f z) * z)"
      using fin pole_is_not_zero[of f]
      by (subst sum.union_disjoint) (auto simp: sum_negf)
    also have " = (z | z  P  (isolated_zero f z  is_pole f z). of_int (zorder f z) * z)"
      by (rule sum.cong) auto
    also have "  Λ"
      using sum_zeros_poles_in_lattice[of 0, folded P_def] .
    finally show ?thesis .
  qed

  define a' where "a' = a - d"
  define A' where "A' = A - {#a#} + {#a'#}"
  have "size A' = elliptic_order f"
    using size A = _ a by (simp add: A'_def size_Suc_Diff1)

  obtain c where c: "z. f z = c * weierstrass_multi_sigma A' B z"
  proof (rule in_terms_of_sigma_explicit[of A' B])
    fix z :: complex
    define P' where "P' = {wP. rel w z}"

    have [simp]: "card P' = 1"
    proof -
      have "P' = {to_fund_parallelogram z}"
        using to_fund_parallelogram_eq_iff[of z] to_fund_parallelogram_unique[of z]
        unfolding P'_def P_def by (auto simp: rel_sym)
      thus ?thesis
        by simp
    qed

    have 1: "count_rel A' z = nat (zorder f z)"
    proof -
      have "rel a a'"
        using d  Λ by (auto simp: a'_def rel_def)
      moreover have "Suc (count_rel A z - Suc 0) = count_rel A z" if "rel a z"
        by (subst Suc_diff_Suc) (use that a in auto simp: count_rel_pos_iff)
      ultimately have "count_rel A' z = count_rel A z"
        using a rel_trans[of a a' z] rel_trans[of a' a z]
        by (auto simp: A'_def count_rel_diff_subset rel_sym)
      also have " = (w | w  P  isolated_zero f w. if rel w z then nat (zorder f w) else 0)"
        by (auto simp: A_def count_rel_sum)
      also have " = (w | w  P'  isolated_zero f z. nat (zorder f w))"
        by (rule sum.mono_neutral_cong_right) (use fin zeros.lattice_cong in auto simp: P'_def)
      also have " = (w(if isolated_zero f z then P' else {}). nat (zorder f z))"
        by (intro sum.cong) 
           (auto intro!: arg_cong[of _ _ nat] zorder.lattice_cong simp: P'_def split: if_splits)
      also have " = nat (zorder f z)"
        using False by (auto simp: isolated_zero_iff_zorder_pos)
      finally show "count_rel A' z = nat (zorder f z)" .
    qed

    have 2: "count_rel B z = nat (-zorder f z)"
    proof -
      have "count_rel B z = (w | w  P  is_pole f w. if rel w z then nat (-zorder f w) else 0)"
        by (auto simp: B_def count_rel_sum)
      also have " = (w | w  P'  is_pole f z. nat (-zorder f w))"
        by (rule sum.mono_neutral_cong_right) (use fin poles.lattice_cong in auto simp: P'_def)
      also have " = (w(if is_pole f z then P' else {}). nat (-zorder f z))"
        by (intro sum.cong) 
           (auto intro!: arg_cong[of _ _ nat] zorder.lattice_cong simp: P'_def split: if_splits)
      also have " = nat (-zorder f z)"
        using False by (auto simp: is_pole_iff_zorder_neg)
      finally show "count_rel B z = nat (-zorder f z)" .
    qed

    show "zorder f z = int (count_rel A' z) - int (count_rel B z)"
      by (simp add: 1 2)
  next
    show "sum_mset A' = sum_mset B"
      using a by (simp add: A'_def sum_mset_diff a'_def d_def)
  qed (use False in auto)

  show ?thesis
  proof (rule that)
    show "size A' = elliptic_order f" "size B = elliptic_order f"
      by fact+
    from d  Λ have "isolated_zero f a'  isolated_zero f a"
      by (intro zeros.lattice_cong) (auto simp: a'_def rel_def uminus_in_lattice_iff)
    thus "isolated_zero f z" if "z ∈# A'" for z
      using that fin a by (auto simp: A'_def A_def set_mset_sum dest!: in_diffD)
    show "is_pole f z" if "z ∈# B" for z
      using that fin by (auto simp: B_def set_mset_sum)
    show "z. f z = c * weierstrass_multi_sigma A' B z"
      by fact
    show "sum_mset A' = sum_mset B"
      using a by (simp add: A'_def sum_mset_diff a'_def d_def)
  qed
qed

lemma (in nicely_elliptic_function) in_terms_of_sigma':
  obtains c A B 
  where "size A = elliptic_order f" "z. z ∈# A  isolated_zero f z"
        "size B = elliptic_order f" "z. z ∈# B  is_pole f z"
        "z. f z = c * weierstrass_multi_sigma A B z"
proof -
  obtain c A B where cAB:
    "size A = elliptic_order f" "z. z ∈# A  isolated_zero f z"
    "size B = elliptic_order f" "z. z ∈# B  is_pole f z"
    "sum_mset A = sum_mset B"
    "z. f z = c * weierstrass_multi_sigma A B z"
    by (rule in_terms_of_sigma) auto
  have disjoint: "¬rel a b" if "a ∈# A" "b ∈# B" for a b
    using cAB(2,4) that pole_is_not_zero poles.lattice_cong by blast

  interpret multi_sigma: nicely_elliptic_multiple_sigma ω1 ω2 A B
    by standard (use cAB disjoint in auto)
  show ?thesis
  proof (rule that[of A B c])
    fix z :: complex
    show "f z = c * weierstrass_multi_sigma A B z" using cAB(6)
      by (rule nicely_meromorphic_on_cosparse_eq_imp_eq) (auto intro!: meromorphic_intros)
  qed fact+
qed


subsection ‹Addition and duplication theorems›

context complex_lattice
begin

theorem weierstrass_sigma_add_diff:
  assumes u: "u  Λ" and v: "v  Λ"
  shows "weierstrass_sigma (u + v) * weierstrass_sigma (u - v) =
           ( v -  u) * weierstrass_sigma u ^ 2 * weierstrass_sigma v ^ 2"
proof -
  write weierstrass_sigma ("σ")
  write weierstrass_eta ("η")

  text ‹
    We consider the following function:
      \[g(u) = \frac{\sigma(u+v)\sigma(u-v)}{\sigma(u)^2\sigma(v)^2} + \wp(u)\]
  ›
  define g where "g = (λu. σ (u + v) * σ (u - v) / (σ u * σ v)2 +  u)"

  text ‹
    Due to the periodicity of ℘› and the quasiperiodicity of σ›, this is an ellpitic function.
  ›
  interpret g: elliptic_function ω1 ω2 g
  proof
    show "g meromorphic_on UNIV"
      by (auto simp: g_def intro!: meromorphic_intros)
         (auto intro!: analytic_on_imp_meromorphic_on analytic_intros)?
  next
    have *: "g (z + ω) = g z" if ω: "ω  Λ" "ω / 2  Λ" for ω z
    proof -
      have "g (z + ω) = σ (z + v + ω) * σ (z - v + ω) / (σ (z + ω) * σ v)2 +  (z + ω)"
        by (simp add: g_def algebra_simps)
      also have " = exp (η ω * (z + v + ω / 2)) * exp (η ω * (z - v + ω / 2)) / 
                        exp (η ω * (z + ω / 2)) ^ 2 *
                      (σ (z + v) * σ (z - v) / (σ z * σ v)2) +  (z + ω)"
        by (subst (1 2 3) weierstrass_sigma_plus_lattice_weak)
           (use ω v in auto simp: weierstrass_sigma_eq_0_iff field_simps)
      also have "exp (η ω * (z + v + ω / 2)) * exp (η ω * (z - v + ω / 2)) /
                   exp (η ω * (z + ω / 2)) ^ 2 = 1"
        unfolding exp_of_nat_mult [symmetric] exp_add [symmetric] exp_diff [symmetric]
        by (simp add: algebra_simps)
      also have " (z + ω) =  z"
        by (rule weierstrass_fun.lattice_cong) (use ω in auto simp: rel_def)
      also have "1 * (σ (z + v) * σ (z - v) / (σ z * σ v)2) +  z = g z"
        by (simp add: g_def)
      finally show ?thesis .
    qed
    show "g (z + ω1) = g z" "g (z + ω2) = g z" for z
      using *[of ω1] *[of ω2] by simp_all
  qed

  text ‹
    Since the constituent functions only have poles at lattice points, our termg cannot have
    poles except at the lattice points either.
  ›
  have no_poles_off_lattice: "¬is_pole g z" if z: "z  Λ" for z
  proof -
    from z have "g analytic_on {z}" using v
      by (auto simp: g_def weierstrass_sigma_eq_0_iff intro!: analytic_intros)
    thus ?thesis
      using analytic_at_imp_no_pole by blast
  qed

  text ‹
    Now comes the most tedious part: we show that the poles at the lattice points cancel.
    Due to ellipticity, it is of course enough to show this for the origin.
  ›
  have "¬is_pole g 0"
  proof
    assume "is_pole g 0"

    define h where "h = (λu. σ (u + v) * σ (u - v) / σ v ^ 2)"
    define F1 where "F1 = fps_expansion h 0"
    have F1: "h has_fps_expansion F1"
      unfolding F1_def using v
      by (intro fps_expansion_intros analytic_at_imp_has_fps_expansion_0)
         (auto simp: h_def weierstrass_sigma_eq_0_iff intro!: analytic_intros)
    have "h 0 = -1" using v 
      by (simp add: h_def weierstrass_sigma_uminus power2_eq_square weierstrass_sigma_eq_0_iff)
    hence "fps_nth F1 0 = -1"
      using has_fps_expansion_imp_0_eq_fps_nth_0[OF F1] by simp
    hence "subdegree F1 = 0"
      by (intro subdegreeI) auto
    have [simp]: "F1  0"
      using fps_nth F1 0 = -1 by auto
    
    define F2 where "F2 = fps_weierstrass_sigma"
    have F2: "σ has_fps_expansion F2"
      unfolding F2_def by (intro fps_expansion_intros)

    define F3 where "F3 = fls_weierstrass"
    define F where "F = fps_to_fls F1 / (fps_to_fls F2)2 + F3"

    have F: "g has_laurent_expansion F"
    proof -
      have "(λu. h u / σ u ^ 2 +  u) has_laurent_expansion F"
        unfolding F_def F3_def using v
        by (intro laurent_expansion_intros F1 F2 has_laurent_expansion_fps)
      also have "(λu. h u / σ u ^ 2 +  u) = g"
        by (simp add: g_def h_def divide_simps fun_eq_iff)
      finally show ?thesis .
    qed
    have [simp]: "F  0"
      using F is_pole g 0 is_pole_0_imp_neg_fls_subdegree by fastforce

    have "fls_subdegree F  -2"
    proof -
      have "(z | z  period_parallelogram 0  is_pole g z. nat (-zorder g z)) = elliptic_order g"
        by (rule g.poles_eq_elliptic_order)
      also have "{z. z  period_parallelogram 0  is_pole g z} = {0}"
      proof (intro equalityI subsetI)
        fix z assume "z  {z. z  period_parallelogram 0  is_pole g z}"
        hence z: "z  period_parallelogram 0" "z  Λ"
          using no_poles_off_lattice by auto
        from z(2) obtain m n where z_eq: "z = of_ω12_coords (of_int m, of_int n)"
          by (auto simp: lattice_def elim!: Ints_cases)
        from z(1) show "z  {0}"
          by (auto simp: period_parallelogram_altdef z_eq)
      qed (use is_pole g 0 in auto)
      finally have "nat (-zorder g 0) = elliptic_order g"
        by simp
      moreover have "g meromorphic_on {0}"
        by (intro meromorphic_on_subset[OF g.meromorphic]) auto
      hence "zorder g 0 < 0" using is_pole g 0
        by (intro isolated_pole_imp_neg_zorder meromorphic_on_isolated_singularity)
      ultimately have "zorder g 0 = -int (elliptic_order g)"
        by simp
      moreover have "elliptic_order g  2"
        using g.elliptic_order_neq_1 g.elliptic_order_eq_0_iff_no_poles is_pole g 0 by auto
      ultimately have "zorder g 0  -2"
        by simp
      also have "zorder g 0 = fls_subdegree F"
        using has_laurent_expansion_zorder_0[OF F] by auto
      finally show ?thesis .
    qed
    moreover have "fls_subdegree F  -2"
    proof -
      have "min (fls_subdegree (fps_to_fls F1 / (fps_to_fls F2)2)) (fls_subdegree F3)  fls_subdegree F"
        unfolding F_def by (rule fls_plus_subdegree) (use F  0 in auto simp: F_def)
      also have "fls_subdegree F3 = -2"
        unfolding F3_def by (simp add: fls_subdegree_weierstrass)
      also have "fls_subdegree (fps_to_fls F1 / (fps_to_fls F2)2) = -2"
        by (subst fls_divide_subdegree) 
           (auto simp: fls_subdegree_fls_to_fps subdegree F1 = _ F2_def
                 simp flip: fps_to_fls_power)
      finally show ?thesis
        by simp
    qed
    ultimately have "fls_subdegree F = -2"
      by linarith

    have "fls_nth F (-2) = 0"
    proof -
      have "fls_nth (fps_to_fls F1 / (fps_to_fls F2)2) (-2) = 
              fls_nth (fps_to_fls F1 / (fps_to_fls F2)2) 
                (fls_subdegree (fps_to_fls F1) - fls_subdegree (fps_to_fls F2 ^ 2))"
        by (simp add: fls_subdegree_fls_to_fps subdegree F1 = _ F2_def
                 flip: fps_to_fls_power)
      also have " = fps_nth F1 0 / fps_nth (F2 ^ 2) (2 * subdegree F2)"
        by (subst fls_divide_nth_base)
           (auto simp: fls_subdegree_fls_to_fps subdegree F1 = _ F2_def
                 simp flip: fps_to_fls_power)
      also have " = -1"
        by (subst fps_pow_base) (auto simp: F2_def fps_nth F1 0 = _)
      finally show ?thesis
        by (simp add: F_def F3_def fls_weierstrass_def)
    qed
    also have "-2 = fls_subdegree F"
      by (rule sym) fact
    finally have "F = 0"
      by simp
    thus False
      using F  0 by contradiction
  qed

  text ‹
    It follows that the function has no poles and must therefore be constant except possibly for
    removable singularities at the lattice points.
  ›
  have no_poles: "¬is_pole g z" for z
  proof (cases "z  Λ")
    case True
    hence "rel z 0"
      by (auto simp: rel_def)
    hence "is_pole g z  is_pole g 0"
      by (rule g.poles.lattice_cong)
    with ¬is_pole g 0 show ?thesis
      by auto
  qed (use no_poles_off_lattice in auto)
  hence "elliptic_order g = 0"
    by (simp add: elliptic_order_def)
  then obtain c where c: "z. g z = c"
    using g.elliptic_order_eq_0_iff_const_cosparse by blast

  text ‹
    By looking at $g(v)$, we determine the constant to be $\wp(v)$, which concludes the proof.
  ›
  hence g_eq: "g z = c" if z: "z  Λ" for z
  proof (rule analytic_on_continuation)
    show "g analytic_on {z}"
      using z v by (auto simp: g_def weierstrass_sigma_eq_0_iff intro!: analytic_intros)
  qed auto
  have c_eq: "c =  v"
    using g_eq[of v] v by (simp add: g_def)

  have "g u =  v"
    using u by (simp add: g_eq c_eq)
  thus ?thesis
    using u v by (simp add: g_def weierstrass_sigma_eq_0_iff field_simps)
qed

text ‹
  From the addition theorem for σ›, it is also easy to derive two related three-term identities:
›
corollary weierstrass_sigma_three_term:
  assumes uvw: "u  Λ" "v  Λ" "w  Λ"
  defines "σ  weierstrass_sigma"
  shows   "σ (u+v) * σ (u-v) * σ w ^ 2 +
           σ (v+w) * σ (v-w) * σ u ^ 2 + 
           σ (w+u) * σ (w-u) * σ v ^ 2 = 0"
  unfolding σ_def
  by (subst (1 2 3) weierstrass_sigma_add_diff) (use uvw in auto simp: algebra_simps)

corollary weierstrass_sigma_sym_three_term:
  assumes uxyz: "u  Λ" "x  Λ" "y  Λ" "z  Λ"
  defines "σ  weierstrass_sigma"
  shows   "σ (u+x) * σ (u-x) * σ (y+z) * σ (y-z) +
           σ (u+y) * σ (u-y) * σ (z+x) * σ (z-x) +
           σ (u+z) * σ (u-z) * σ (x+y) * σ (x-y) = 0"
proof -
  have *: "a * b * c * d = (a * b) * (c * d)" for a b c d :: complex
    by (simp add: mult_ac)
  show ?thesis unfolding σ_def *
    by (subst (1 2 3 4 5 6) weierstrass_sigma_add_diff) (use uxyz in auto simp: algebra_simps)
qed

text ‹
  Taking the derivative of the addition formula for σ› and setting $v = u$, we obtain the
  duplication formula:
›
theorem weierstrass_sigma_duplication:
  assumes u: "u  Λ" "2 * u  Λ"
  shows "weierstrass_sigma (2 * u) = -℘' u * weierstrass_sigma u ^ 4"
proof -
  write weierstrass_sigma ("σ")
  write weierstrass_zeta ("ζ")
  write weierstrass_eta ("η")

  define S where "S = σ  (λv. u - v)"
  have [derivative_intros]: "(S has_field_derivative (1 * (-1))) (at u)" unfolding S_def 
    by (intro DERIV_chain) (auto intro!: derivative_eq_intros has_field_derivative_weierstrass_sigma_0)
  have [simp]: "S u = 0"
    by (simp add: S_def)
  define g where "g = (λv. σ (u + v) * S v - ( v -  u) * σ u ^ 2 * σ v ^ 2)"
  note [derivative_intros] = has_field_derivative_weierstrass_sigma_0[folded S_def]

  have "(g has_field_derivative (-σ (2 * u) - ℘' u * (σ u)2 * (σ u)2)) (at u)"
    unfolding g_def using u by (auto intro!: derivative_eq_intros)
  also have "?this  ((λ_. 0) has_field_derivative (-σ (2 * u) - ℘' u * (σ u)2 * (σ u)2)) (at u)"
  proof (rule DERIV_cong_ev)
    have "eventually (λv. v  -Λ) (nhds u)"
      by (rule eventually_nhds_in_open) (use closed_lattice u in auto)
    thus "eventually (λv. g v = 0) (nhds u)"
    proof eventually_elim
      case (elim v)
      thus ?case
        unfolding g_def S_def using weierstrass_sigma_add_diff[of u v] u by simp
    qed
  qed auto
  finally have "((λ_. 0) has_field_derivative (-σ (2 * u) - ℘' u * σ u ^ 4)) (at u)"
    by (simp add: power_numeral_reduce mult_ac)
  moreover have "((λ_. 0) has_field_derivative 0) (at u)" by simp
  ultimately have "-σ (2 * u) - ℘' u * σ u ^ 4 = 0"
    using DERIV_unique by blast
  thus ?thesis
    by Groebner_Basis.algebra
qed


text ‹
  By taking the logarithmic derivative of the addition formula for $\sigma$, 
  we obtain the addition formula for $\zeta$. This is only well-defined if $u\neq \pm v$.
›
theorem weierstrass_zeta_add:
  assumes u: "u  Λ" and v: "v  Λ" and "¬rel u v" "¬rel u (-v)"
  shows "weierstrass_zeta (u + v) = 
           weierstrass_zeta u + weierstrass_zeta v + 1 / 2 * (℘' v - ℘' u) / ( v -  u)"
proof -
  have uv: "u + v  Λ" "u - v  Λ"
    using assms weierstrass_fun.lattice_cong'[of u v] by (auto simp: rel_def)
  have " u   v"
    using assms weierstrass_fun_eq_iff by simp
  write weierstrass_sigma ("σ")
  write weierstrass_zeta ("ζ")
  write weierstrass_eta ("η")

  define g where "g = (λu v. σ (u + v) * σ (u - v) / (( v -  u) * σ u ^ 2 * σ v ^ 2))"
  have g1: "g u v = 1"
    using weierstrass_sigma_add_diff[of u v] u v uv by (auto simp: g_def weierstrass_sigma_eq_0_iff)

  have g2: "eventually (λu. g u v = 1) (at u)"
  proof -
    show ?thesis
      using eventually_not_rel_at[of 0 u] eventually_not_rel_at[of v u]
            eventually_not_rel_at[of "-v" u]
    proof eventually_elim
      case (elim u)
      thus ?case
        using weierstrass_sigma_add_diff[of u v] v
        by (auto simp: g_def weierstrass_sigma_eq_0_iff rel_def)
    qed
  qed

  have g3: "eventually (λv. g u v = 1) (at v)"
  proof -
    show ?thesis
      using eventually_not_rel_at[of 0 v] eventually_not_rel_at[of u v]
            eventually_not_rel_at[of "-u" v]
    proof eventually_elim
      case (elim v)
      thus ?case
        using weierstrass_sigma_add_diff[of u v] u
        by (auto simp: g_def weierstrass_sigma_eq_0_iff rel_def add_ac diff_in_lattice_commute)
    qed
  qed

  have 1: "ζ (u - v) = -ζ (u + v) + 2 * ζ u - ℘' u / ( v -  u)"
  proof -
    define h where "h = (λu.  v -  u)"
    define D where "D = (ζ (u + v) + ζ (u - v) + ℘' u / ( v -  u) - 2 * ζ u)"
    have [derivative_intros]: "(h has_log_derivative (-℘' u / ( v -  u))) (at u)"
      using u  u   v unfolding h_def
      by (intro has_field_derivative_imp_has_log_derivative) (auto intro!: derivative_eq_intros)
    have "((λu. σ (u + v) * σ (u - v) / (h u * σ u ^ 2 * σ v ^ 2)) has_log_derivative D) (at u)"
      using u v uv by (auto intro!: derivative_eq_intros simp: weierstrass_sigma_eq_0_iff D_def)
    also have "?this  ((λ_. 1) has_log_derivative D) (at u)"
    proof (rule has_log_derivative_cong_ev)
      show "F u in at u. σ (u + v) * σ (u - v) / (h u * (σ u)2 * (σ v)2) = 1"
        using g2 by eventually_elim (auto simp: g_def h_def)
    qed (use g1 in auto simp: h_def g_def)
    finally have "((λ_. 1) has_log_derivative D) (at u)" .
    moreover have "((λ_. 1) has_log_derivative 0) (at u)"
      by auto
    ultimately have "D = 0"
      by (rule has_log_derivative_unique) auto
    thus ?thesis
      by (simp add: D_def algebra_simps)
  qed

  have 2: "ζ (u - v) = ζ (u + v) - 2 * ζ v - ℘' v / ( v -  u)"
  proof -
    define h where "h = (λv.  v -  u)"
    define D where "D = (ζ (u + v) - ζ (u - v) - ℘' v / ( v -  u) - 2 * ζ v)"
    have [derivative_intros]: "(h has_log_derivative (℘' v / ( v -  u))) (at v)"
      using u v  u   v unfolding h_def
      by (intro has_field_derivative_imp_has_log_derivative) (auto intro!: derivative_eq_intros)
    have "((λv. σ (u + v) * σ (u - v) / (h v * σ u ^ 2 * σ v ^ 2)) has_log_derivative D) (at v)"
      using u v uv by (auto intro!: derivative_eq_intros simp: weierstrass_sigma_eq_0_iff D_def)
    also have "?this  ((λ_. 1) has_log_derivative D) (at v)"
    proof (rule has_log_derivative_cong_ev)
      show "F v in at v. σ (u + v) * σ (u - v) / (h v * (σ u)2 * (σ v)2) = 1"
        using g3 by eventually_elim (auto simp: g_def h_def)
    qed (use g1 in auto simp: h_def g_def)
    finally have "((λ_. 1) has_log_derivative D) (at v)" .
    moreover have "((λ_. 1) has_log_derivative 0) (at v)"
      by auto
    ultimately have "D = 0"
      by (rule has_log_derivative_unique) auto
    thus ?thesis
      by (simp add: D_def algebra_simps)
  qed

  have "-ζ (u + v) + 2 * ζ u - ℘' u / ( v -  u) = ζ (u + v) - 2 * ζ v - ℘' v / ( v -  u)"
    using 1 2 by simp
  hence "2 * ζ (u + v) = 2 * (ζ u + ζ v) + ℘' v / ( v -  u) - ℘' u / ( v -  u)"
    by Groebner_Basis.algebra
  also have " = 2 * (ζ u + ζ v) + (℘' v - ℘' u) / ( v -  u)"
    by (simp add: divide_simps)
  also have " = 2 * (ζ u + ζ v + 1/2 * (℘' v - ℘' u) / ( v -  u))"
    by (simp add: algebra_simps)
  finally show "ζ (u + v) = ζ u + ζ v + 1 / 2 * (℘' v - ℘' u) / ( v -  u)"
    by Groebner_Basis.algebra
qed

text ‹
  Taking the logarithmic derivative of the duplication identity for $\sigma$ gives us the
  one for $\zeta$:
›
theorem weierstrass_zeta_duplication:
  assumes u: "u  Λ" "2 * u  Λ"
  shows "weierstrass_zeta (2 * u) = 2 * weierstrass_zeta u + (3 *  u ^ 2 - 15 * G 4) / ℘' u"
proof -
  write weierstrass_sigma ("σ")
  write weierstrass_zeta ("ζ")

  define g where "g = (λu. -σ (2 * u) / (℘' u * σ u ^ 4))"
  define D where "D = 2 * ζ (2 * u) - (6 * ( u)2 - 30 * G 4) / ℘' u - 4 * ζ u"
  have [derivative_intros]: "(℘' has_log_derivative ((6 *  u ^ 2 - 30 * G 4) / ℘' u)) (at u)"
    using u by (auto intro!: has_field_derivative_imp_has_log_derivative derivative_eq_intros 
                     simp: weierstrass_fun_deriv_eq_0_iff)
  have "(g has_log_derivative D) (at u)"
    using u by (auto simp: D_def g_def weierstrass_sigma_eq_0_iff intro!: derivative_eq_intros)
  also have "?this  ((λ_. 1) has_log_derivative D) (at u)"
  proof (rule has_log_derivative_cong_ev')
    have "eventually (λu. u  -Λ) (nhds u)"
      by (intro eventually_nhds_in_open) (use closed_lattice u in auto)
    moreover have "eventually (λu. u  (λw. 2 * w) -` (-Λ)) (nhds u)"
      by (intro eventually_nhds_in_open open_vimage continuous_intros) (use u closed_lattice in auto)
    ultimately show "eventually (λu. g u = 1) (nhds u)"
    proof eventually_elim
      case (elim u)
      thus ?case
        using weierstrass_sigma_duplication[of u] by (auto simp: g_def weierstrass_sigma_eq_0_iff)
    qed
  qed
  finally have "((λ_. 1) has_log_derivative D) (at u)" .
  moreover have "((λ_. 1) has_log_derivative 0) (at u)" by auto
  ultimately have "D = 0"
    by (rule has_log_derivative_unique) auto
  hence "2 * ζ (2 * u) - 2 * ((3 *  u ^ 2 - 15 * G 4) / ℘' u) = 4 * ζ u"
    by (simp add: D_def)
  thus ?thesis
    by Groebner_Basis.algebra
qed

end

end