Theory Tangent_Numbers

section ‹Tangent numbers›
theory Tangent_Numbers
imports
  "HOL-Computational_Algebra.Computational_Algebra"
  "Bernoulli.Bernoulli_FPS" 
  "Polynomial_Interpolation.Ring_Hom_Poly"
  Boustrophedon_Transform_Library
  Alternating_Permutations
begin

subsection ‹The higher derivatives of $\tan x$›

text ‹
  The $n$-th derivatives of $\tan x$ are:

     $\tan x ^ 2 + 1$

     $\tan x ^ 3 + \tan x$

     $6 \tan x ^ 4 + 8 \tan x ^ 2 + 2$

     $24 \tan x ^ 5 + 40 \tan x ^ 3 + 16 \tan x$

     \dots

  No pattern is readily apparent, but it is obvious that for any $n$, the $n$-th derivative of 
  $\tan x$ can be expressed as a polynomial of degree $n+1$ in $\tan x$, i.e.\ it is of the form
  $P_n(\tan x)$ for some family of polynomials $P_n$.

  Using the fact that $\tan' x = \tan x ^ 2 + 1$ and the chain rule, one can deduce that
  $P_{n+1}(X) = (X^2 + 1)P_n'(X)$, and of course $P_0(X) = X$, which gives us a recursive 
  characterisation of $P_n$.
›
primrec tangent_poly :: "nat  nat poly" where
  "tangent_poly 0 = [:0, 1:]"
| "tangent_poly (Suc n) = pderiv (tangent_poly n) * [:1,0,1:]"

lemma degree_tangent_poly [simp]: "degree (tangent_poly n) = n + 1"
  by (induction n)
     (auto simp: degree_mult_eq pderiv_eq_0_iff degree_pderiv simp del: mult_pCons_right)

lemma tangent_poly_altdef [code]:
  "tangent_poly n = ((λp. pderiv p * [:1,0,1:]) ^^ n) [:0, 1:]"
  by (induction n) simp_all

lemma fps_tan_higher_deriv':
  "(fps_deriv ^^ n) (fps_tan (1::'a::field_char_0)) = 
     fps_compose (fps_of_poly (map_poly of_nat (tangent_poly n))) (fps_tan 1)"
proof -
  interpret of_nat_poly_hom: map_poly_comm_semiring_hom of_nat
    by standard auto
  show ?thesis
    by (induction n)
       (simp_all add: hom_distribs fps_of_poly_pderiv fps_of_poly_add
                      fps_of_poly_pCons fps_compose_add_distrib fps_compose_mult_distrib
                      fps_compose_deriv fps_tan_deriv' power2_eq_square of_nat_poly_pderiv)
qed

theorem fps_tan_higher_deriv:
  "(fps_deriv ^^ n) (fps_tan 1) = 
     poly (map_poly of_int (tangent_poly n)) (fps_tan (1::'a::field_char_0))"
  using fps_tan_higher_deriv'[of n]
  by (subst (asm) fps_compose_of_poly)
     (simp_all add: map_poly_map_poly o_def fps_of_nat)

text ‹
  For easier notation, we give the name ``auxiliary tangent numbers'' to the coefficients of
  these polynomials and treat them as a number triangle $T_{n,j}$. These will aid us in
  the computation of the actual tangent numbers later.
›
definition tangent_number_aux :: "nat  nat  nat" where
  "tangent_number_aux n j = poly.coeff (tangent_poly n) j"

text ‹
  The coefficients satisfy the following recurrence and boundary conditions:

     $T_{0, 1} = 1$

     $T_{0, j} = 0$ if $j\neq 1$

     $T_{n, j} = 0$ if $j > n+1$ or $n+j$ even

     $T_{n, n+1} = n!$

     $T_{n+1, j+1} = j T_{n,j} + (j+2) T_{n, j+2}$
›
lemma tangent_number_aux_0_left:
  "tangent_number_aux 0 j = (if j = 1 then 1 else 0)"
  unfolding tangent_number_aux_def by (auto simp: coeff_pCons split: nat.splits)

lemma tangent_number_aux_0_left' [simp]:
  "j  1  tangent_number_aux 0 j = 0"
  "tangent_number_aux 0 (Suc 0) = 1"
  by (simp_all add: tangent_number_aux_0_left)

lemma tangent_number_aux_0_right:
  "tangent_number_aux (Suc n) 0 = poly.coeff (tangent_poly n) 1"
  unfolding tangent_number_aux_def tangent_poly.simps by (auto simp: coeff_pderiv)

lemma tangent_number_aux_rec:
  "tangent_number_aux (Suc n) (Suc j) = j * tangent_number_aux n j + (j + 2) * tangent_number_aux n (j + 2)"
  unfolding tangent_number_aux_def tangent_poly.simps
  by (simp_all add: coeff_pderiv coeff_pCons split: nat.splits)

lemma tangent_number_aux_rec':
  "n > 0  j > 0  tangent_number_aux n j = (j-1) * tangent_number_aux (n-1) (j-1) + (j+1) * tangent_number_aux (n-1) (j+1)"
  using tangent_number_aux_rec[of "n-1" "j-1"] by simp

lemma tangent_number_aux_odd_eq_0: "even (n + j)  tangent_number_aux n j = 0"
  unfolding tangent_number_aux_def
  by (induction n arbitrary: j)
     (auto simp: coeff_pCons coeff_pderiv split: nat.splits)

lemma tangent_number_aux_eq_0 [simp]: "j > n + 1  tangent_number_aux n j = 0"
  unfolding tangent_number_aux_def by (simp add: coeff_eq_0)

lemma tangent_number_aux_last [simp]: "tangent_number_aux n (Suc n) = fact n"
  by (induction n) (auto simp: tangent_number_aux_rec)

lemma tangent_number_aux_last': "Suc m = n  tangent_number_aux m n = fact m"
  by (cases n) auto

lemma tangent_number_aux_1_right [simp]:
  "tangent_number_aux i (Suc 0) = tangent_number_aux (i + 1) 0"
  by (simp add: tangent_number_aux_def coeff_pderiv)


subsection ‹The tangent numbers›

text ‹
  The actual secant numbers $T_n$ are now defined to be the even-index coefficients of the
  power series expansion of $\tan x$ (the even-index ones are all $0$).~\oeiscite{A000182}

  This also turns out to be exactly the same as $T_{n, 0}$.
›
definition tangent_number :: "nat  nat" where
  "tangent_number n = nat (floor (fps_nth (fps_tan 1) (2*n-1) * fact (2*n-1) :: real))"

lemma tangent_number_conv_zigzag_number:
  "n > 0  tangent_number n = zigzag_number (2 * n - 1)"
  unfolding tangent_number_def
  by (subst zigzag_number_conv_fps_tan [symmetric]) auto

lemma tangent_number_0 [simp]: "tangent_number 0 = 0"
  by (simp add: tangent_number_def fps_tan_def)

lemma fps_nth_tan_aux:
  "fps_tan (1::'a::field_char_0) $ (2*n-1) = 
     of_nat (tangent_number_aux (2*n-1) 0) / fact (2*n-1)"
proof (cases "n = 0")
  case False
  interpret of_nat_poly_hom: map_poly_comm_semiring_hom of_nat
    by standard auto
  from False have n: "n > 0"
    by simp
  have "fps_nth ((fps_deriv ^^ (2 * n - 1)) (fps_tan (1::'a))) 0 = 
          fact (2*n-1) * fps_nth (fps_tan 1) (2*n-1)"
    by (simp add: fps_0th_higher_deriv)
  also have "(fps_deriv ^^ (2*n-1)) (fps_tan (1::'a)) =
               fps_of_poly (map_poly of_nat (tangent_poly (2*n-1))) oo fps_tan 1"
    by (subst fps_tan_higher_deriv') auto
  also have "fps_nth  0 = of_nat (tangent_number_aux (2*n-1) 0)"
    by (simp add: tangent_number_aux_def)
  finally show ?thesis
    by simp
qed auto

lemma fps_nth_tan:
  "fps_nth (fps_tan (1::'a :: field_char_0)) (2*n - Suc 0) = of_int (tangent_number n) / fact (2*n-1)"
  using fps_nth_tan_aux[of n, where ?'a = real] fps_nth_tan_aux[of n, where ?'a = 'a]
  by (simp add: tangent_number_def)

lemma tangent_number_conv_aux [code]:
  "tangent_number n = tangent_number_aux (2*n - Suc 0) 0"
  using fps_nth_tan[of n, where ?'a = real] fps_nth_tan_aux[of n, where ?'a = real] by simp

lemma tangent_number_1 [simp]: "tangent_number (Suc 0) = 1"
  by (simp add: tangent_number_conv_aux tangent_number_aux_0_right)


text ‹
  The tangent number $T_n$ can be expressed in terms of the Bernoulli number $\mathcal{B}_n$:
›
theorem tangent_number_conv_bernoulli:
   "2 * real n * of_int (tangent_number n) =
      (-1)^(n+1) * (2^(2*n) * (2^(2*n) - 1)) * bernoulli (2*n)"
proof -
  define F where "F = (λc::complex. fps_compose bernoulli_fps (fps_const c * fps_X))"
  define E where "E = (λc::complex. fps_to_fls (fps_exp c))"
  have neqI1: "f  g" if "fls_nth f 0  fls_nth g 0" for f g :: "complex fls"
    using that by metis
  have [simp]: "fls_nth (E c) n = c ^ nat n / (fact (nat n))" if "n  0" for n c
    using that by (auto simp: E_def)

  have [simp]: "subdegree (1 - fps_exp 1 :: complex fps) = 1"
    by (rule subdegreeI) auto
  have "fps_to_fls (F (2*𝗂) - F (4*𝗂) - fps_const 𝗂 * fps_X) = 
          2 * fls_const 𝗂 * fls_X / (E (2*𝗂) - 1) -
          4 * fls_const 𝗂 * fls_X / (E (4*𝗂) - 1) -
          fls_const 𝗂 * fls_X"
    unfolding F_def bernoulli_fps_def E_def
    apply (simp flip: fls_compose_fps_to_fls)
    apply (simp add: fls_compose_fps_divide fls_times_fps_to_fls fls_compose_fps_diff
                flip: fls_const_mult_const fls_divide_fps_to_fls)
    done
  also have "E (4 * 𝗂) = E (2 * 𝗂) ^ 2"
    by (simp add: fps_exp_power_mult E_def flip: fps_to_fls_power)
  also have "E (2 * 𝗂) ^ 2 - 1 = (E (2 * 𝗂) - 1) * (E (2 * 𝗂) + 1)"
    by (simp add: algebra_simps power2_eq_square)
  also have "2 * fls_const 𝗂 * fls_X / (E (2 * 𝗂) - 1) -
             4 * fls_const 𝗂 * fls_X / ((E (2 * 𝗂) - 1) * (E (2 * 𝗂) + 1)) =
             2 * fls_const 𝗂 * fls_X * (1 / (E (2 * 𝗂) + 1))"
    unfolding E_def
    apply (simp add: divide_simps)
    apply (auto simp: algebra_simps add_eq_0_iff fls_times_fps_to_fls neqI1)
    done
  also have "1 / (E (2 * 𝗂) + 1) = E (-𝗂) / (E (-𝗂) * (E (2 * 𝗂) + 1))"
    by (simp add: divide_simps add_eq_0_iff2 neqI1)
  also have "E (-𝗂) * (E (2 * 𝗂) + 1) = E 𝗂 + E (-𝗂)"
    by (simp add: E_def algebra_simps flip: fls_times_fps_to_fls fps_exp_add_mult)
  also have "2 * fls_const 𝗂 * fls_X * (E (-𝗂) / (E 𝗂 + E (-𝗂))) - fls_const 𝗂 * fls_X =
             fls_X * (fls_const (-𝗂) * (1 - 2 * E (-𝗂) / (E 𝗂 + E (-𝗂))))"
    by (simp add: algebra_simps)
  also have "1 - 2 * E (-𝗂) / (E 𝗂 + E (-𝗂)) = (E 𝗂 - E (-𝗂)) / (E 𝗂 + E (-𝗂))"
    by (simp add: divide_simps neqI1)
  also have "fls_const (-𝗂) *  = (-fls_const 𝗂/2 * (E 𝗂 - E (-𝗂))) / ((E 𝗂 + E (-𝗂)) / 2)"
    by (simp add: divide_simps neqI1)
  also have "-fls_const 𝗂 / 2 * (E 𝗂 - E (-𝗂)) = fps_to_fls (fps_sin 1)"
    by (simp add: fps_sin_fps_exp_ii E_def fls_times_fps_to_fls flip: fls_const_divide_const)
  also have "(E 𝗂 + E (-𝗂)) / 2 = fps_to_fls (fps_cos 1)"
    by (simp add: fps_cos_fps_exp_ii E_def fls_times_fps_to_fls flip: fls_const_divide_const)
  also have "fls_X * (fps_to_fls (fps_sin 1) / fps_to_fls (fps_cos 1)) = 
               fps_to_fls (fps_X * fps_tan (1::complex))"
    by (simp add: fps_tan_def fls_times_fps_to_fls flip: fls_divide_fps_to_fls)
  finally have eq: "F (2 * 𝗂) - F (4 * 𝗂) - fps_const 𝗂 * fps_X =
                    fps_X * fps_tan 1" (is "?lhs = ?rhs") 
    by (simp only: fps_to_fls_eq_iff)

  show "2 * real n * of_int (tangent_number n) =
          (-1)^(n+1) * (2^(2*n) * (2^(2*n) - 1)) * bernoulli (2*n)"
  proof (cases "n = 0")
    case False
    hence n: "n > 0"
      by simp
    have "fps_nth ?lhs (2*n) = (-1)^n * (2^(2*n) - 4^(2*n)) * of_real (bernoulli (2 * n)) / fact (2*n)"
      using n unfolding F_def fps_nth_compose_linear fps_sub_nth
      by (simp add: algebra_simps diff_divide_distrib)
    also note ?lhs = ?rhs
    also have "fps_nth ?rhs (2*n) = complex_of_int (tangent_number n) / fact (2 * n - 1)"
      using n by (simp add: fps_nth_tan)
    finally have "complex_of_int (tangent_number n) * (fact (2*n) / fact (2 * n - 1)) =
                  (- 1) ^ n * (2 ^ (2 * n) - 4 ^ (2 * n)) * complex_of_real (bernoulli (2 * n))"
      by (simp add: divide_simps)
    also have "complex_of_int (tangent_number n) * (fact (2*n) / fact (2 * n - 1)) =
               of_real (fact (2*n) / fact (2 * n - 1) * of_int (tangent_number n))"
      by (simp add: field_simps)
    also have "fact (2*n) / fact (2 * n - 1) = (2 * of_nat n :: real)"
      using fact_binomial[of 1 "2 * n", where ?'a = real] n by simp
    also have "2 ^ (2 * n) - 4 ^ (2 * n) = -(2 ^ (2 * n) * (2 ^ (2 * n) - 1 :: complex))"
      by (simp add: algebra_simps flip: power_mult_distrib)
    also have "(- 1) ^ n * - (2 ^ (2 * n) * (2 ^ (2 * n) - 1)) * complex_of_real (bernoulli (2 * n)) =
               of_real ((-1)^(n+1) * (2^(2*n) * (2^(2*n) - 1)) * bernoulli (2*n))"
      by simp
    finally show ?thesis
      by (simp only: of_real_eq_iff)
  qed auto
qed


subsection ‹Efficient functional computation›

text ‹
  We will now formalise and verify an algorithm to compute the first $n$ tangent numbers 
  relatively efficiently via the auxiliary tangent numbers. The algorithm is a functional 
  variant of the imperative in-place algorithm given by Brent et al.~citebrent. The 
  functional algorithm could easily be adapted to one that returns a stream of all tangent numbers
  instead of a list of the first $n$ of them.

  The algorithm uses $O(n^2)$ additions and multiplications on integers, but since the numbers
  grow up to $\Theta(n \log n)$ bits, this translates to $O(n^3 \log{1+\varepsilon} n)$
  bit operations.

  Note that Brent et al.\ only define the tangent numbers $T_n$ starting with $n = 1$, whereas
  we also defined $T_0 = 0$. The algorithm only computes $T_1, \ldots, T_n$.
›

function pochhammer_row_impl :: "nat  nat  nat  nat list" where
  "pochhammer_row_impl k n x = (if k  n then [] else x # pochhammer_row_impl (Suc k) n (x * k))"
  by auto
termination by (relation "measure (λ(k,n,_)  n - k)") auto

lemmas [simp del] = pochhammer_row_impl.simps

lemma pochhammer_rec'': "k > 0  pochhammer n k = n * pochhammer (n+1) (k-1)"
  by (cases k) (auto simp: pochhammer_rec)

lemma pochhammer_row_impl_correct:
  "pochhammer_row_impl k n x = map (λi. x * pochhammer k i) [0..<n-k]"
proof (induction k n x rule: pochhammer_row_impl.induct)
  case (1 k n x)
  show ?case
  proof (cases "k < n")
    case True
    have "pochhammer_row_impl k n x = x # map (λi. x * k * pochhammer (Suc k) i) [0..<n - (k + 1)]"
      using True by (subst pochhammer_row_impl.simps) (simp_all add: "1.IH")
    also have "map (λi. x * k * pochhammer (Suc k) i) [0..<n - (k + 1)] =
               map (λi. x * pochhammer k i) (map Suc [0..<n - (k + 1)])"
      by (simp add: pochhammer_rec)
    also have "map Suc [0..<n - (k + 1)] = [Suc 0..<n-k]"
      using True by (simp add: map_Suc_upt Suc_diff_Suc del: upt_Suc)
    also have "x # map (λi. x * pochhammer k i) [Suc 0..<n-k] =
               map (λi. x * pochhammer k i) (0 # [Suc 0..<n-k])"
      by simp
    also have "0 # [Suc 0..<n-k] = [0..<n-k]"
      using True by (subst upt_conv_Cons) auto
    finally show ?thesis .
  qed (subst pochhammer_row_impl.simps; auto)
qed


context
  fixes T :: "nat  nat  nat"
  defines "T  tangent_number_aux"
begin

primrec tangent_number_impl_aux1 :: "nat  nat  nat list  nat list" where
  "tangent_number_impl_aux1 j y [] = []"
| "tangent_number_impl_aux1 j y (x # xs) =
     (let x' = j * y + (j+2) * x in x' # tangent_number_impl_aux1 (j+1) x' xs)"

lemma length_tangent_number_impl_aux1 [simp]: "length (tangent_number_impl_aux1 j y xs) = length xs"
  by (induction xs arbitrary: j y) (simp_all add: Let_def)

fun tangent_number_impl_aux2 :: "nat list  nat list" where
  "tangent_number_impl_aux2 [] = []"
| "tangent_number_impl_aux2 (x # xs) = x # tangent_number_impl_aux2 (tangent_number_impl_aux1 0 x xs)"

lemma tangent_number_impl_aux1_nth_eq:
  assumes "i < length xs"
  shows   "tangent_number_impl_aux1 j y xs ! i =
             (j+i) * (if i = 0 then y else tangent_number_impl_aux1 j y xs ! (i-1)) + (j+i+2) * xs ! i"
  using assms
proof (induction xs arbitrary: i j y)
  case (Cons x xs)
  show ?case
  proof (cases i)
    case 0
    thus ?thesis
      by (simp add: Let_def)
  next
    case (Suc i')
    define x' where "x' = j * y + (x + (x + j * x))"
    have "tangent_number_impl_aux1 j y (x # xs) ! i = tangent_number_impl_aux1 (Suc j) x' xs ! i'"
      by (simp add: x'_def Let_def Suc)
    also have " = (Suc j + i') * (if i' = 0 then x' else tangent_number_impl_aux1 (Suc j) x' xs ! (i'-1)) + 
                    (Suc j + i' + 2) * xs ! i'"
      using Cons.prems by (subst Cons.IH) (auto simp: Suc)
    also have "Suc j + i' = j + i"
      by (simp add: Suc)
    also have "xs ! i' = (x # xs) ! i"
      by (auto simp: Suc)
    also have "(if i' = 0 then x' else tangent_number_impl_aux1 (Suc j) x' xs ! (i'-1)) =
               (x' # tangent_number_impl_aux1 j y (x # xs)) ! i"
      by (auto simp: Suc x'_def Let_def)
    finally show ?thesis
      by (simp add: Suc)
  qed
qed auto

lemma tangent_number_impl_aux2_correct:
  assumes "k  n"
  shows  "tangent_number_impl_aux2 (map (λi. T (2 * k + i) (i + 1)) [0..<n-k]) =
             map tangent_number [Suc k..<Suc n]"
  using assms
proof (induction k rule: inc_induct)
  case (step k)
  have *: "[0..<n-k] = 0 # map Suc [0..<n-Suc k]"
    by (subst upt_conv_Cons)
       (use step.hyps in auto simp: map_Suc_upt Suc_diff_Suc simp del: upt_Suc)
  define ts where 
    "ts = tangent_number_impl_aux1 0 (T (2*k) 1) (map (λi. T (2*k+i+1) (i+2)) [0..<n-Suc k])"
  have T_rec: "T (Suc a) (Suc b) = b * T a b + (b + 2) * T a (b + 2)" for a b
    unfolding T_def tangent_number_aux_rec ..

  have "tangent_number_impl_aux2 (map (λi. T (2 * k + i) (i + 1)) [0..<n-k]) =
          T (2 * k) 1 # tangent_number_impl_aux2 ts"
    unfolding * list.map tangent_number_impl_aux2.simps
    by (simp add: o_def ts_def algebra_simps numeral_3_eq_3)
  also have "ts = map (λi. T (2 * Suc k + i) (i + 1)) [0..<n - Suc k]"
  proof (rule nth_equalityI)
    fix i assume "i < length ts"
    hence i: "i < n - Suc k"
      by (simp add: ts_def)
    hence "ts ! i = T (2 * Suc k + i) (i + 1)"
    proof (induction i)
      case 0
      thus ?case unfolding ts_def
        by (subst tangent_number_impl_aux1_nth_eq)
           (use T_rec[of "2*k+1" 0] in auto simp: eval_nat_numeral)
    next
      case (Suc i)
      have "ts ! Suc i = Suc i * T (Suc (Suc (2 * k + i))) (Suc i) +
                (Suc i + 2) * T (Suc (Suc (2 * k + i))) (Suc i + 2)"
        using Suc unfolding ts_def
        by (subst tangent_number_impl_aux1_nth_eq) (auto simp: eval_nat_numeral)
      also have " = T (2 * Suc k + Suc i) (Suc i + 1)"
        using T_rec[of "2 * Suc k + i" "Suc i"] by simp
      finally show ?case .
    qed
    thus "ts ! i = map (λi. T (2 * Suc k + i) (i + 1)) [0..<n - Suc k] ! i"
      using i by simp
  qed (simp_all add: ts_def)
  also have "tangent_number_impl_aux2  = map tangent_number [Suc (Suc k)..<Suc n]"
    by (rule step.IH)
  also have "T (2 * k) 1 = tangent_number (Suc k)"
    by (simp add: tangent_number_conv_aux T_def)
  also have "tangent_number (Suc k) # map tangent_number [Suc (Suc k)..<Suc n] =
             map tangent_number [Suc k..<Suc n]"
    using step.hyps by (subst upt_conv_Cons) (auto simp del: upt_Suc)
  finally show ?case .
qed auto

definition tangent_numbers :: "nat  nat list" where
  "tangent_numbers n = map tangent_number [1..<Suc n]"

lemma tangent_numbers_code [code]:
  "tangent_numbers n = tangent_number_impl_aux2 (pochhammer_row_impl 1 (Suc n) 1)"
proof -
  have "pochhammer_row_impl 1 (Suc n) 1 = map (λi. T i (i + 1)) [0..<n]"
    by (simp add: pochhammer_row_impl_correct pochhammer_fact T_def)
  also have "tangent_number_impl_aux2  = map tangent_number [Suc 0..<Suc n]"
    using tangent_number_impl_aux2_correct[of 0 n] by (simp del: upt_Suc)
  finally show ?thesis
    by (simp only: tangent_numbers_def One_nat_def)
qed

lemma tangent_number_code [code]:
  "tangent_number n = (if n = 0 then 0 else last (tangent_numbers n))"
  by (simp add: tangent_numbers_def)

end

end