import number_theory.bernoulli

--OUTPUT 1
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  apply nat.strong_induction_on n,
  clear n,
  assume n hn x,
  -- We want to prove that for all $m < d$, we have $B_{m} (1 + x) = B_{m} (x) + m x^{m - 1}$
  assume h1 : ∀ m : ℕ, m < n → (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1),
  -- We want to show that $$B_{d} (1 + x) = B_{d} (x) + d x^{d - 1}$$
  have h2 : (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1),
  {
    -- Multiplying both sides by $d + 1$, and using the fact that, for all $l \in \mathbb{N}$, $\sum_{k = 0}^{l} {l + 1 \choose k} B_k = (l + 1) X^l$ (where $B_k$ is the $k$-th Bernoulli number), we get that
    have h3 : (n + 1) * (1 + x)^n - (n + 1) * x^n = (n + 1) * ∑ (l : ℕ) in finset.range (n + 1), (n + 1) * (finset.range (n + 1)).sum (λ (k : ℕ) (hk : k ∈ finset.range (n + 1)), (n + 1) * (polynomial.bernoulli k).eval x^k) * x^(l - 1),
    {
      apply polynomial.ext,
      assume i,
      -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
      have h4 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * ∑ (l : ℕ) in finset.range (n + 1), (n + 1) * (finset.range (n + 1)).sum (λ (k : ℕ) (hk : k ∈ finset.range (n + 1)), (n + 1) * (polynomial.bernoulli k).eval x^k) * x^(l - 1),
      {
        -- We want to prove that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
        have h5 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * (1 + x)^i - (n + 1) * x^i, from by auto,
        -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
        have h6 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * (1 + x)^i - (n + 1) * x^i, from by auto [mul_sub],
        -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
        have h7 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * (1 + x)^i - (n + 1) * x^i, from by auto [mul_sub],
        -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
        have h8 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * (1 + x)^i - (n + 1) * x^i, from by auto [mul_sub],
        -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
        have h9 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * (1 + x)^i - (n + 1) * x^i, from by auto [mul_sub],
        -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
        have h10 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * (1 + x)^i - (n + 1) * x^i, from by auto [mul_sub],
        -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
        have h11 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * (1 + x)^i - (n + 1) * x^i, from by auto [mul_sub],
        -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
        have h12 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * (1 + x)^i - (n + 1) * x^i, from by auto [mul_sub],
        -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
        have h13 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * (1 + x)^i - (n + 1) * x^i, from by auto [mul_sub],
        -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
        have h14 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * (1 + x)^i - (n + 1) * x^i, from by auto [mul_sub],
        -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
        have h15 : (n + 1) * (1 + x)^i - (n + 1) * x^i = (n + 1) * (1 + x)^i - (n + 1) * x^i, from by auto [mul_sub],
        -- We want to show that $$(d + 1) (1 + x)^i - (d + 1) x^i = \sum_{l = 0}^{d} {
end --Needs more than 2000 tokens!

--OUTPUT 2
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  -- We apply strong induction on $n$
  by_cases (n = 0),
  {
    assume h0 : n = 0,
    show (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1), from by auto [h0, polynomial.bernoulli_zero]
  },
  {
    assume h0 : n ≠ 0,
    have h1 : ∀ (m : ℕ) (h : m < n), (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [lt_of_le_of_ne, polynomial.bernoulli_eval_one_add_lemma],
    have h2 : ∀ (m : ℕ) (h : m > n), (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [lt_of_le_of_ne, polynomial.bernoulli_eval_one_add_lemma],
    have h3 : ∀ (m : ℕ) (h : m = n), (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [lt_of_le_of_ne, polynomial.bernoulli_eval_one_add_lemma],
    show (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1), from by auto [h1, h2, h3, lt_irrefl, nat.not_succ_le_zero, nat.not_succ_le_self, nat.succ_pos, lt_or_eq_of_le, ne.def, eq.symm, lt_of_lt_of_le, lt_of_le_of_ne]
  },
end

--OUTPUT 3
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  -- apply strong induction on $n$
  apply nat.strong_induction_on n,
  assume n (ih : ∀ m : ℕ, m < n → (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1)),
  show (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1),
  -- multiply both sides by $d + 1$
  calc (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + (n + 1) * x^n : by auto [bernoulli_eval_add]
  -- Using the fact that, for all $l \in \mathbb{N}$, $\sum_{k = 0}^{l} {l + 1 \choose k} B_k = (l + 1) X^l$
  ... = (polynomial.bernoulli n).eval x + (n + 1) * x^n : by rw [nat.sum_eq_sum_binomial, add_comm, polynomial.eval_pow, polynomial.eval_C],
  -- The conclusion then follows easily.
  ... = (polynomial.bernoulli n).eval x + n * x^(n - 1) : by rw [nat.sub_add_cancel, nat.sub_add_cancel]
end

--OUTPUT 4
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  -- We apply strong induction on $n$.
  induction n with d hd,
  -- So, for all $m < d$, we have $B_{m} (1 + x) = B_{m} (x) + m x^{m - 1}$
  -- and we want to show that $$B_{d} (1 + x) = B_{d} (x) + d x^{d - 1}$$
  have h1 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h2 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h3 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h4 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h5 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h6 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h7 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h8 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h9 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h10 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h11 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h12 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h13 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h14 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h15 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h16 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h17 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h18 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h19 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h20 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h21 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h22 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h23 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h24 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h25 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h26 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h27 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h28 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h29 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h30 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h31 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h32 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h33 : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by auto [hd],
  have h34 : ∀ m < d, (polynomial.bernoulli
end --Needs more than 2000 tokens!

--OUTPUT 5
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  have h1 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli m = (m + 1) * polynomial.X^m - ∑ l in finset.range (m + 1), (m + 1) * (finset.range (m + 1)).sum (λ (k : ℕ), (m + 1) * (polynomial.X^k) * polynomial.bernoulli k / (k + 1)), from
    by auto [polynomial.eval_sum, polynomial.eval_pow, polynomial.eval_X, polynomial.eval_C, polynomial.X_ne_zero, polynomial.eval_smul, polynomial.eval_add, polynomial.eval_sub, polynomial.eval_C],
  have h2 : ∀ (m : ℕ) (x : ℚ), (m + 1) * polynomial.X^m - ∑ l in finset.range (m + 1), (m + 1) * (finset.range (m + 1)).sum (λ (k : ℕ), (m + 1) * (polynomial.X^k) * polynomial.bernoulli k / (k + 1)) = m * polynomial.X^m - ∑ l in finset.range m, m * (finset.range m).sum (λ (k : ℕ), m * (polynomial.X^k) * polynomial.bernoulli k / (k + 1)), from
    by auto [polynomial.eval_sum, polynomial.eval_pow, polynomial.eval_X, polynomial.eval_C, polynomial.X_ne_zero, polynomial.eval_smul, polynomial.eval_add, polynomial.eval_sub, polynomial.eval_C],
  have h3 : ∀ (m : ℕ) (x : ℚ), (m + 1) * polynomial.X^m - ∑ l in finset.range m, m * (finset.range m).sum (λ (k : ℕ), m * (polynomial.X^k) * polynomial.bernoulli k / (k + 1)) = m * polynomial.X^m - ∑ l in finset.range m, m * (finset.range m).sum (λ (k : ℕ), m * (polynomial.X^k) * polynomial.bernoulli k / (k + 1)) + polynomial.X^m + m * polynomial.X^(m - 1) * polynomial.bernoulli m / (m + 1), from
    by auto [polynomial.eval_sum, polynomial.eval_pow, polynomial.eval_X, polynomial.eval_C, polynomial.X_ne_zero, polynomial.eval_smul, polynomial.eval_add, polynomial.eval_sub, polynomial.eval_C],
  have h4 : ∀ (m : ℕ) (x : ℚ), m * polynomial.X^m - ∑ l in finset.range m, m * (finset.range m).sum (λ (k : ℕ), m * (polynomial.X^k) * polynomial.bernoulli k / (k + 1)) + polynomial.X^m + m * polynomial.X^(m - 1) * polynomial.bernoulli m / (m + 1) = polynomial.X^m + m * polynomial.X^(m - 1) * polynomial.bernoulli m / (m + 1), from
    by auto [polynomial.eval_sum, polynomial.eval_pow, polynomial.eval_X, polynomial.eval_C, polynomial.X_ne_zero, polynomial.eval_smul, polynomial.eval_add, polynomial.eval_sub, polynomial.eval_C],
  have h5 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli m = polynomial.X^m + m * polynomial.X^(m - 1) * polynomial.bernoulli m / (m + 1), from
    by auto [h1, h2, h3, h4],
  have h6 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli m = polynomial.X^m + m * polynomial.X^(m - 1) * polynomial.bernoulli m / (m + 1), from
    by auto [h5],
  have h7 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli (m + 1) = polynomial.bernoulli m + (m + 1) * polynomial.X^m * polynomial.bernoulli (m + 1) / (m + 2), from
    by auto [h6],
  have h8 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli (m + 1) = polynomial.bernoulli m + (m + 1) * polynomial.X^m * polynomial.bernoulli (m + 1) / (m + 2), from
    by auto [h7],
  have h9 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli (m + 1) = polynomial.bernoulli m + (m + 1) * polynomial.X^m * polynomial.bernoulli (m + 1) / (m + 2), from
    by auto [h8],
  have h10 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli (m + 1) = polynomial.bernoulli m + (m + 1) * polynomial.X^m * polynomial.bernoulli (m + 1) / (m + 2), from
    by auto [h9],
  have h11 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli (m + 1) = polynomial.bernoulli m + (m + 1) * polynomial.X^m * polynomial.bernoulli (m + 1) / (m + 2), from
    by auto [h10],
  have h12 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli (m + 1) = polynomial.bernoulli m + (m + 1) * polynomial.X^m * polynomial.bernoulli (m + 1) / (m + 2), from
    by auto [h11],
  have h13 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli (m + 1) = polynomial.bernoulli m + (m + 1) * polynomial.X^m * polynomial.bernoulli (m + 1) / (m + 2), from
    by auto [h12],
  have h14 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli (m + 1) = polynomial.bernoulli m + (m + 1) * polynomial.X^m * polynomial.bernoulli (m + 1) / (m + 2), from
    by auto [h13],
  have h15 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli (m + 1) = polynomial.bernoulli m + (m + 1) * polynomial.X^m * polynomial.bernoulli (m + 1) / (m + 2), from
    by auto [h14],
  have h16 : ∀ (m : ℕ) (x : ℚ), polynomial.bernoulli (m + 1) = polynomial.bernoulli m + (m + 1) * polynomial.X^m * polynomial.bernoulli (m + 1) / (m + 2), from
    by auto [h15],
  have h17 : ∀ (m :
end --Needs more than 2000 tokens!

--OUTPUT 6
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  have h1 : (∀ m : ℕ, m < n → (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1)),
  { assume m,
    assume hm : m < n,
    induction m with m IH hm,
    { -- base case
      show (polynomial.bernoulli 0).eval (1 + x) = (polynomial.bernoulli 0).eval x + 0 * x^(0 - 1),
        by auto using [bernoulli_zero, eval_zero, eval_one, zero_add, zero_mul]
    },
    { -- inductive step
      have hm1 : m < n, from nat.lt_of_succ_lt hm,
      have h1 : (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from IH hm1,
      have h2 : (polynomial.bernoulli (m + 1)).eval (1 + x) = (polynomial.bernoulli (m + 1)).eval x + (m + 1) * x^((m + 1) - 1),
      {
        calc (polynomial.bernoulli (m + 1)).eval (1 + x) = (polynomial.bernoulli (m + 1)).eval x + m * (1 + x)^(m + 1) - (m + 1) * x^(m + 1) : by auto [bernoulli_add_one, eval_add, eval_mul, eval_pow]
        ... = (polynomial.bernoulli (m + 1)).eval x + m * (1 + x)^m * (1 + x) - (m + 1) * x^(m + 1) : by rw nat.succ_eq_add_one
        ... = (polynomial.bernoulli (m + 1)).eval x + m * (1 + x)^m * (1 + x) - (m + 1) * x^m * x : by rw nat.succ_eq_add_one
        ... = (polynomial.bernoulli (m + 1)).eval x + m * (1 + x)^m * (1 + x) - m * x^m * x - x^m * x : by rw nat.add_sub_cancel
        ... = (polynomial.bernoulli (m + 1)).eval x + m * (1 + x)^m * (1 + x) - m * x^m * x - x^m * x : by rw mul_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * (1 + x)^m * 1 - m * x^m * x - x^m * x : by rw mul_one
        ... = (polynomial.bernoulli (m + 1)).eval x + m * (1 + x)^m * 1 - m * x^m * x - x^m * x : by rw one_mul
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m * 1) - m * x^m * x - x^m * x : by rw mul_one
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m * 1) - (m * x^m * x + x^m * x) : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m * 1) - m * x^m * x - x^m * x : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m * 1) - (m * x^m * x + x^m * x) : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m * 1) - (m * x^m + x^m) * x : by rw mul_add
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m * 1) - m * x^m * x - x^m * x : by rw mul_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - m * x^m * x - x^m * x : by rw mul_one
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - (m * x^m * x + x^m * x) : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - (m * x^m * x + x^m * x) : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - m * x^m * x - x^m * x : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - (m * x^m * x + x^m * x) : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - (m * x^m + x^m) * x : by rw mul_add
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - m * x^m * x - x^m * x : by rw mul_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - (m * x^m * x + x^m * x) : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - (m * x^m + x^m) * x : by rw mul_add
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - m * x^m * x - x^m * x : by rw mul_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - (m * x^m * x + x^m * x) : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - ((m * x^m * x + x^m * x) + (m * ((1 + x)^m) - (m * x^m * x + x^m * x))) : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - ((m * ((1 + x)^m) + (m * x^m * x + x^m * x)) + (m * ((1 + x)^m) - (m * x^m * x + x^m * x))) : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - (m * ((1 + x)^m) + (m * ((1 + x)^m) - (m * x^m * x + x^m * x))) : by rw add_comm
        ... = (polynomial.bernoulli (m + 1)).eval x + m * ((1 + x)^m) - (m * ((1 + x)^m) + ((m * ((1 + x)^m) - (m * x^m * x + x^m * x)) + (m * x^m * x + x^m * x))) : by rw add_comm
        ... = (polynomial.bernoulli (
end --Needs more than 2000 tokens!

--OUTPUT 7
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  -- We apply strong induction on $n$
  apply nat.strong_induction_on n,
  -- So, for all $m < d$, we have $B_{m} (1 + x) = B_{m} (x) + m x^{m - 1}$ and we want to show that $$B_{d} (1 + x) = B_{d} (x) + d x^{d - 1}$$
  assume d,
  assume h : ∀ m : ℕ, m < d → (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1),
  -- Multiplying both sides by $d + 1$, and using the fact that, for all $l \in \mathbb{N}$, $\sum_{k = 0}^{l} {l + 1 \choose k} B_k = (l + 1) X^l$ (where $B_k$ is the $k$-th Bernoulli number), we get that
  have h1 : (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval x = (d + 1) * (x^d),
  begin
    -- Multiplying both sides by $d + 1$
    calc (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval x = (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval x : by auto [ring]
    ... = (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval (1 + x + -x) : by auto [ring]
    ... = (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval (1 + x) + (d + 1) * (polynomial.bernoulli d).eval x : by auto [ring]
    ... = (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval (1 + x) + (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval x : by auto [ring]
    ... = (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval (1 + x) + (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval x : by auto [ring]
    ... = (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval (1 + x) + (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval (1 + x) - (d + 1) * (polynomial.bernoulli d).eval (1 + x) + (d + 1) * (polynomial.bernoulli d).eval x : by auto [ring]
    ... = (d + 1) * ((polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval x) : by auto [ring]
    ... = (d + 1) * ((polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval x) : by auto [ring]
    ... = (d + 1) * ((polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval x) : by auto [ring]
    ... = (d + 1) * ((polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval x) : by auto [ring]
    ... = (d + 1) * ((polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval x) : by auto [ring]
    ... = (d + 1) * ((polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval x) : by auto [ring]
    ... = (d + 1) * ((polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bernoulli d).eval (1 + x) + (polynomial.bernoulli d).eval (1 + x) - (polynomial.bern
end --Needs more than 2000 tokens!

--OUTPUT 8
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  induction n with d hd,
  {
    -- base case
    show (polynomial.bernoulli 0).eval (1 + x) = (polynomial.bernoulli 0).eval x + 0 * x^(0 - 1), from by auto [ring],
  },
  {
    -- inductive case
    have h1 : (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + d * x^(d - 1), from hd,
    have h2 : (d + 1) * (polynomial.bernoulli (d + 1)).eval (1 + x) = (d + 1) * (polynomial.bernoulli (d + 1)).eval x + (d + 1) * (d + 1) * x^(d + 1 - 1), by auto [ring, h1],
    have h3 : (d + 1) * (polynomial.bernoulli (d + 1)).eval (1 + x) = (d + 1) * (polynomial.bernoulli (d + 1)).eval x + (d + 1) * (d + 1) * x^(d), from by auto [ring],
    have h4 : (d + 1) * (polynomial.bernoulli (d + 1)).eval (1 + x) - (d + 1) * (polynomial.bernoulli (d + 1)).eval x = (d + 1) * (d + 1) * x^(d), from by auto [ring],
    have h5 : (d + 1) * (polynomial.bernoulli (d + 1)).eval (1 + x) - (d + 1) * (polynomial.bernoulli (d + 1)).eval x = (d + 1) * (d + 1 - 1) * x^(d - 1) + (d + 1) * x^(d - 1), from by auto [ring],
    have h6 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (d + 1 - 1) * x^(d - 1) + (d + 1) * x^(d - 1), from by auto [ring, h4],
    have h7 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (d + 1 - 1) * x^(d - 1) + (d + 1) * x^(d - 1), from by auto [ring],
    have h8 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (d * x^(d - 1)) + (d + 1) * x^(d - 1), from by auto [ring],
    have h9 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = d * (d + 1) * x^(d - 1) + (d + 1) * x^(d - 1), from by auto [ring],
    have h10 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * ((d + 1) * x^(d - 1) + x^(d - 1)), from by auto [ring],
    have h11 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (x^(d - 1) + d * x^(d - 1) + x^(d - 1)), from by auto [ring],
    have h12 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * x^(d - 1) + (d + 1) * d * x^(d - 1) + (d + 1) * x^(d - 1), from by auto [ring],
    have h13 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * x^(d - 1) + (d + 1) * d * x^(d - 1) + (d + 1) * x^(d - 1), from by auto [ring],
    have h14 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * x^(d - 1) + (d + 1) * x^(d - 1) + (d + 1) * d * x^(d - 1), from by auto [ring],
    have h15 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * x^(d - 1) + (d + 1) * x^(d - 1) + (d + 1) * d * x^(d - 1), from by auto [ring],
    have h16 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * x^(d - 1) + (d + 1) * (x^(d - 1) + d * x^(d - 1)), from by auto [ring],
    have h17 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * x^(d - 1) + (d + 1) * ((d + 1) * x^(d - 1)), from by auto [ring],
    have h18 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * x^(d - 1) + (d + 1)^2 * x^(d - 1), from by auto [ring],
    have h19 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * x^(d - 1) + (d + 1)^2 * x^(d - 1), from by auto [ring],
    have h20 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (x^(d - 1) + (d + 1) * x^(d - 1)), from by auto [ring],
    have h21 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (x^(d - 1) + (d + 1) * x^(d - 1)), from by auto [ring],
    have h22 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (x^(d - 1) + (d + 1) * x^(d - 1)), from by auto [ring],
    have h23 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (x^(d - 1) + (d + 1) * x^(d - 1)), from by auto [ring],
    have h24 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (x^(d - 1) + (d + 1) * x^(d - 1)), from by auto [ring],
    have h25 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (x^(d - 1) + (d + 1) * x^(d - 1)), from by auto [ring],
    have h26 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (x^(d - 1) + (d + 1) * x^(d - 1)), from by auto [ring],
    have h27 : (d + 1) * ((1 + x)^(d + 1) - x^(d + 1)) = (d + 1) * (x^(d - 1) + (d + 1) * x
end --Needs more than 2000 tokens!

/- FEW SHOT PROMPTS TO CODEX(START)
/--`theorem`
Power Set is Closed under Intersection
Let $S$ be a set.

Let $\powerset S$ be the power set of $S$.

Then:
:$\forall A, B \in \powerset S: A \cap B \in \powerset S$
`proof`
Let $A, B \in \powerset S$.

Then by the definition of power set, $A \subseteq S$ and $B \subseteq S$.

From Intersection is Subset we have that $A \cap B \subseteq A$.

It follows from Subset Relation is Transitive that $A \cap B \subseteq S$.

Thus $A \cap B \in \powerset S$ and closure is proved.
{{qed}}
-/
theorem power_set_intersection_closed {α : Type*} (S : set α) : ∀ A B ∈ 𝒫 S, (A ∩ B) ∈ 𝒫 S :=
begin
  -- $A$ and $B$ are sets. $A$ and $B$ belong to power set of $S$
  assume (A : set α) (hA : A ∈ 𝒫 S) (B : set α) (hB : B ∈ 𝒫 S),
  -- Then $A ⊆ S$ and $B ⊆ S$, by power set definition
  have h1 : (A ⊆ S) ∧ (B ⊆ S), from by auto [set.subset_of_mem_powerset, set.subset_of_mem_powerset],
  -- Then $(A ∩ B) ⊆ A$, by intersection of set is a subset
  have h2 : (A ∩ B) ⊆ A, from by auto [set.inter_subset_left],
  -- Then $(A ∩ B) ⊆ S$, by subset relation is transitive
  have h3 : (A ∩ B) ⊆ S, from by auto [set.subset.trans],
  -- Hence $(A ∩ B) ∈  𝒫 S$, by power set definition
  show (A ∩ B) ∈  𝒫 S, from by auto [set.mem_powerset],
end

/--`theorem`
Square of Sum
 :$\forall x, y \in \R: \paren {x + y}^2 = x^2 + 2 x y + y^2$
`proof`
Follows from the distribution of multiplication over addition:

{{begin-eqn}}
{{eqn | l = \left({x + y}\right)^2
      | r = \left({x + y}\right) \cdot \left({x + y}\right)
}}
{{eqn | r = x \cdot \left({x + y}\right) + y \cdot \left({x + y}\right)
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x \cdot x + x \cdot y + y \cdot x + y \cdot y
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x^2 + 2xy + y^2
      | c =
}}
{{end-eqn}}
{{qed}}
-/
theorem square_of_sum (x y : ℝ) : (x + y)^2 = (x^2 + 2*x*y + y^2) :=
begin
  -- expand the power
  calc (x + y)^2 = (x+y)*(x+y) : by auto [sq]
  -- distributive property of multiplication over addition gives:
  ... = x*(x+y) + y*(x+y) : by auto [add_mul]
  -- applying the above property further gives:
  ... = x*x + x*y + y*x + y*y : by auto [mul_comm, add_mul] using [ring]
  -- rearranging the terms using commutativity and adding gives:
  ... = x^2 + 2*x*y + y^2 : by auto [sq, mul_comm] using [ring]
end

/--`theorem`
Identity of Group is Unique
Let $\struct {G, \circ}$ be a group. Then there is a unique identity element $e \in G$.
`proof`
From Group has Latin Square Property, there exists a unique $x \in G$ such that:
:$a x = b$

and there exists a unique $y \in G$ such that:
:$y a = b$

Setting $b = a$, this becomes:

There exists a unique $x \in G$ such that:
:$a x = a$

and there exists a unique $y \in G$ such that:
:$y a = a$

These $x$ and $y$ are both $e$, by definition of identity element.
{{qed}}
-/
theorem group_identity_unique {G : Type*} [group G] : ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a :=
begin
  -- Group has Latin Square Property
  have h1 : ∀ a b : G, ∃! x : G, a * x = b, from by auto using [use (a⁻¹ * b)],
  have h2 : ∀ a b : G, ∃! y : G, y * a = b, from by auto using [use b * a⁻¹],

  -- Setting $b = a$, this becomes:
  have h3 : ∀ a : G, ∃! x : G, a * x = a, from by auto [h1],
  have h4 : ∀ a : G, ∃! y : G, y * a = a, from by auto [h2],

  -- These $x$ and $y$ are both $(1 : G)$, by definition of identity element
  have h5 : ∀ a : G, classical.some (h3 a).exists = (1 : G), from by auto [exists_unique.unique, h3, classical.some_spec, exists_unique.exists, mul_one],
  have h6 : ∀ a : G, classical.some (h4 a).exists = (1 : G), from by auto [exists_unique.unique, h4, classical.some_spec, exists_unique.exists, one_mul],

  show ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a, from by auto [h3, h4, exists_unique.unique, classical.some_spec, exists_unique.exists] using [use (1 : G)],
end

/--`theorem`
Bernoulli polynomial evaluation
Given a natural number $n$ and a rational $x$, let $B_n (x)$ denote the $n$-th Bernoulli polynomial evaluated at $x$. Then, $$B_n (1 + x) = B_n (x) + n x^{n - 1}$$
`proof`
We apply strong induction on $n$. So, for all $m < d$, we have $B_{m} (1 + x) = B_{m} (x) + m x^{m - 1}$ and we want to show that $$B_{d} (1 + x) = B_{d} (x) + d x^{d - 1}$$
Multiplying both sides by $d + 1$, and using the fact that, for all $l \in \mathbb{N}$, $\sum_{k = 0}^{l} {l + 1 \choose k} B_k = (l + 1) X^l$ (where $B_k$ is the $k$-th Bernoulli number), we get that
$$ (d + 1) (1 + x)^d - (d + 1) x^d = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
The conclusion then follows easily.

QED

-/
theorem  bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
FEW SHOT PROMPTS TO CODEX(END)-/


/-
Copyright (c) 2018 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin
-/
import deprecated.subgroup
import deprecated.group
import ring_theory.subring.basic

/-!
# Unbundled subrings (deprecated)

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

This file is deprecated, and is no longer imported by anything in mathlib other than other
deprecated files, and test files. You should not need to import it.

This file defines predicates for unbundled subrings. Instead of using this file, please use
`subring`, defined in `ring_theory.subring.basic`, for subrings of rings.

## Main definitions

`is_subring (S : set R) : Prop` : the predicate that `S` is the underlying set of a subring
of the ring `R`. The bundled variant `subring R` should be used in preference to this.

## Tags

is_subring
-/
universes u v

open group

variables {R : Type u} [ring R]

/-- `S` is a subring: a set containing 1 and closed under multiplication, addition and additive
inverse. -/
structure is_subring (S : set R) extends is_add_subgroup S, is_submonoid S : Prop.

/-- Construct a `subring` from a set satisfying `is_subring`. -/
def is_subring.subring {S : set R} (hs : is_subring S) : subring R :=
{ carrier := S,
  one_mem' := hs.one_mem,
  mul_mem' := λ _ _, hs.mul_mem,
  zero_mem' := hs.zero_mem,
  add_mem' := λ _ _, hs.add_mem,
  neg_mem' := λ _, hs.neg_mem }

namespace ring_hom

lemma is_subring_preimage {R : Type u} {S : Type v} [ring R] [ring S]
  (f : R →+* S) {s : set S} (hs : is_subring s) : is_subring (f ⁻¹' s) :=
{ ..is_add_group_hom.preimage f.to_is_add_group_hom hs.to_is_add_subgroup,
  ..is_submonoid.preimage f.to_is_monoid_hom hs.to_is_submonoid, }

lemma is_subring_image {R : Type u} {S : Type v} [ring R] [ring S]
  (f : R →+* S) {s : set R} (hs : is_subring s) : is_subring (f '' s) :=
{ ..is_add_group_hom.image_add_subgroup f.to_is_add_group_hom hs.to_is_add_subgroup,
  ..is_submonoid.image f.to_is_monoid_hom hs.to_is_submonoid, }

lemma is_subring_set_range {R : Type u} {S : Type v} [ring R] [ring S]
  (f : R →+* S) : is_subring (set.range f) :=
{ ..is_add_group_hom.range_add_subgroup f.to_is_add_group_hom,
  ..range.is_submonoid f.to_is_monoid_hom, }

end ring_hom

variables {cR : Type u} [comm_ring cR]

lemma is_subring.inter {S₁ S₂ : set R} (hS₁ : is_subring S₁) (hS₂ : is_subring S₂) :
  is_subring (S₁ ∩ S₂) :=
{ ..is_add_subgroup.inter hS₁.to_is_add_subgroup hS₂.to_is_add_subgroup,
  ..is_submonoid.inter hS₁.to_is_submonoid hS₂.to_is_submonoid }

lemma is_subring.Inter {ι : Sort*} {S : ι → set R} (h : ∀ y : ι, is_subring (S y)) :
  is_subring (set.Inter S) :=
{ ..is_add_subgroup.Inter (λ i, (h i).to_is_add_subgroup),
  ..is_submonoid.Inter (λ i, (h i).to_is_submonoid) }

lemma is_subring_Union_of_directed {ι : Type*} [hι : nonempty ι]
  {s : ι → set R} (h : ∀ i, is_subring (s i))
  (directed : ∀ i j, ∃ k, s i ⊆ s k ∧ s j ⊆ s k) :
  is_subring (⋃i, s i) :=
{ to_is_add_subgroup := is_add_subgroup_Union_of_directed
    (λ i, (h i).to_is_add_subgroup) directed,
  to_is_submonoid := is_submonoid_Union_of_directed (λ i, (h i).to_is_submonoid) directed }

namespace ring

/-- The smallest subring containing a given subset of a ring, considered as a set. This function
is deprecated; use `subring.closure`. -/
def closure (s : set R) := add_group.closure (monoid.closure s)

variable {s : set R}

local attribute [reducible] closure

theorem exists_list_of_mem_closure {a : R} (h : a ∈ closure s) :
  (∃ L : list (list R), (∀ l ∈ L, ∀ x ∈ l, x ∈ s ∨ x = (-1:R)) ∧ (L.map list.prod).sum = a) :=
add_group.in_closure.rec_on h
  (λ x hx, match x, monoid.exists_list_of_mem_closure hx with
    | _, ⟨L, h1, rfl⟩ := ⟨[L], list.forall_mem_singleton.2 (λ r hr, or.inl (h1 r hr)), zero_add _⟩
    end)
  ⟨[], list.forall_mem_nil _, rfl⟩
  (λ b _ ih, match b, ih with
    | _, ⟨L1, h1, rfl⟩ := ⟨L1.map (list.cons (-1)),
      λ L2 h2, match L2, list.mem_map.1 h2 with
        | _, ⟨L3, h3, rfl⟩ := list.forall_mem_cons.2 ⟨or.inr rfl, h1 L3 h3⟩
        end,
      by simp only [list.map_map, (∘), list.prod_cons, neg_one_mul];
      exact list.rec_on L1 neg_zero.symm (λ hd tl ih,
        by rw [list.map_cons, list.sum_cons, ih, list.map_cons, list.sum_cons, neg_add])⟩
    end)
  (λ r1 r2 hr1 hr2 ih1 ih2, match r1, r2, ih1, ih2 with
    | _, _, ⟨L1, h1, rfl⟩, ⟨L2, h2, rfl⟩ := ⟨L1 ++ L2, list.forall_mem_append.2 ⟨h1, h2⟩,
      by rw [list.map_append, list.sum_append]⟩
    end)

@[elab_as_eliminator]
protected theorem in_closure.rec_on {C : R → Prop} {x : R} (hx : x ∈ closure s)
  (h1 : C 1) (hneg1 : C (-1)) (hs : ∀ z ∈ s, ∀ n, C n → C (z * n))
  (ha : ∀ {x y}, C x → C y → C (x + y)) : C x :=
begin
  have h0 : C 0 := add_neg_self (1:R) ▸ ha h1 hneg1,
  rcases exists_list_of_mem_closure hx with ⟨L, HL, rfl⟩, clear hx,
  induction L with hd tl ih, { exact h0 },
  rw list.forall_mem_cons at HL,
  suffices : C (list.prod hd),
  { rw [list.map_cons, list.sum_cons],
    exact ha this (ih HL.2) },
  replace HL := HL.1, clear ih tl,
  rsuffices ⟨L, HL', HP | HP⟩ : ∃ L : list R,
    (∀ x ∈ L, x ∈ s) ∧ (list.prod hd = list.prod L ∨ list.prod hd = -list.prod L),
  { rw HP, clear HP HL hd, induction L with hd tl ih, { exact h1 },
    rw list.forall_mem_cons at HL',
    rw list.prod_cons,
    exact hs _ HL'.1 _ (ih HL'.2) },
  { rw HP, clear HP HL hd, induction L with hd tl ih, { exact hneg1 },
    rw [list.prod_cons, neg_mul_eq_mul_neg],
    rw list.forall_mem_cons at HL',
    exact hs _ HL'.1 _ (ih HL'.2) },
  induction hd with hd tl ih,
  { exact ⟨[], list.forall_mem_nil _, or.inl rfl⟩ },
  rw list.forall_mem_cons at HL,
  rcases ih HL.2 with ⟨L, HL', HP | HP⟩; cases HL.1 with hhd hhd,
  { exact ⟨hd :: L, list.forall_mem_cons.2 ⟨hhd, HL'⟩, or.inl $
      by rw [list.prod_cons, list.prod_cons, HP]⟩ },
  { exact ⟨L, HL', or.inr $ by rw [list.prod_cons, hhd, neg_one_mul, HP]⟩ },
  { exact ⟨hd :: L, list.forall_mem_cons.2 ⟨hhd, HL'⟩, or.inr $
      by rw [list.prod_cons, list.prod_cons, HP, neg_mul_eq_mul_neg]⟩ },
  { exact ⟨L, HL', or.inl $ by rw [list.prod_cons, hhd, HP, neg_one_mul, neg_neg]⟩ }
end

lemma closure.is_subring : is_subring (closure s) :=
{ one_mem := add_group.mem_closure $ is_submonoid.one_mem $ monoid.closure.is_submonoid _,
  mul_mem := λ a b ha hb, add_group.in_closure.rec_on hb
    ( λ c hc, add_group.in_closure.rec_on ha
      ( λ d hd, add_group.subset_closure ((monoid.closure.is_submonoid _).mul_mem hd hc))
      ( (zero_mul c).symm ▸ (add_group.closure.is_add_subgroup _).zero_mem)
      ( λ d hd hdc, neg_mul_eq_neg_mul d c ▸ (add_group.closure.is_add_subgroup _).neg_mem hdc)
      ( λ d e hd he hdc hec, (add_mul d e c).symm ▸
        ((add_group.closure.is_add_subgroup _).add_mem hdc hec)))
    ( (mul_zero a).symm ▸ (add_group.closure.is_add_subgroup _).zero_mem)
    ( λ c hc hac, neg_mul_eq_mul_neg a c ▸ (add_group.closure.is_add_subgroup _).neg_mem hac)
    ( λ c d hc hd hac had, (mul_add a c d).symm ▸
      (add_group.closure.is_add_subgroup _).add_mem hac had),
  ..add_group.closure.is_add_subgroup _}

theorem mem_closure {a : R} : a ∈ s → a ∈ closure s :=
add_group.mem_closure ∘ @monoid.subset_closure _ _ _ _

theorem subset_closure : s ⊆ closure s :=
λ _, mem_closure

theorem closure_subset {t : set R} (ht : is_subring t) : s ⊆ t → closure s ⊆ t :=
(add_group.closure_subset ht.to_is_add_subgroup) ∘ (monoid.closure_subset ht.to_is_submonoid)

theorem closure_subset_iff {s t : set R} (ht : is_subring t) : closure s ⊆ t ↔ s ⊆ t :=
(add_group.closure_subset_iff ht.to_is_add_subgroup).trans
  ⟨set.subset.trans monoid.subset_closure, monoid.closure_subset ht.to_is_submonoid⟩

theorem closure_mono {s t : set R} (H : s ⊆ t) : closure s ⊆ closure t :=
closure_subset closure.is_subring $ set.subset.trans H subset_closure

lemma image_closure {S : Type*} [ring S] (f : R →+* S) (s : set R) :
  f '' closure s = closure (f '' s) :=
le_antisymm
  begin
    rintros _ ⟨x, hx, rfl⟩,
    apply in_closure.rec_on hx; intros,
    { rw [f.map_one], apply closure.is_subring.to_is_submonoid.one_mem },
    { rw [f.map_neg, f.map_one],
      apply closure.is_subring.to_is_add_subgroup.neg_mem,
      apply closure.is_subring.to_is_submonoid.one_mem },
    { rw [f.map_mul],
      apply closure.is_subring.to_is_submonoid.mul_mem;
      solve_by_elim [subset_closure, set.mem_image_of_mem] },
    { rw [f.map_add], apply closure.is_subring.to_is_add_submonoid.add_mem, assumption' },
  end
  (closure_subset (ring_hom.is_subring_image _ closure.is_subring) $
    set.image_subset _ subset_closure)

end ring