import tactic.interactive
import tactic.monotonicity
import data.finset
/-
 To do: also document derive_handlers
-/

#check tactic.interactive.ac_mono
-- Deduce inequalities using known monotonicity properties of functions
#check tactic.interactive.abstract
-- Tactical to record the argument used as a lemma, as well as solving the goal
#check tactic.interactive.ac_reflexivity
-- Proves an equation using commutativity and associativity
#check tactic.interactive.admit
-- Sorry
#check tactic.interactive.all_goals
-- Apply a tactic to all goals
#check tactic.interactive.any_goals
-- Apply a tactic to any goal where it works
#check tactic.interactive.apply
--
#check tactic.interactive.apply_assumption
-- Like apply, but looks in the local context for applicable things
#check tactic.interactive.apply_auto_param
-- Not used directly in mathlib, but used indirectly by the `tidy` tactic.
#check tactic.interactive.apply_field
--
#check tactic.interactive.apply_instance
-- Use type class resolution to close the main goal.  For example, if we alread
-- have group structures on G and H, and the goal is to give a group structure
-- on G × (ℕ → H), then `apply_instance` will close the goal.
#check tactic.interactive.apply_opt_param
-- Not used directly in mathlib
#check tactic.interactive.apply_rules
-- This is like `apply`, except that you supply a list of rules instead of
-- a single rule, and Lean applies any of the rules that are applicable.
#check tactic.interactive.apply_with
-- Like apply, but with configurable options
#check tactic.interactive.assume
-- Like intro, but type can be specified
#check tactic.interactive.assumption
--
#check tactic.interactive.assumption'
-- Try to apply `assumption` to all goals
#check tactic.interactive.async
--
#check tactic.interactive.by_cases
--
#check tactic.interactive.by_contra
-- Same as by_contradiction
#check tactic.interactive.by_contradiction
--
#check tactic.interactive.case
#check tactic.interactive.cases
#check tactic.interactive.cases_matching
#check tactic.interactive.cases_type
#check tactic.interactive.casesm
#check tactic.interactive.change
#check tactic.interactive.choose
#check tactic.interactive.clarify
#check tactic.interactive.classical
#check tactic.interactive.clean
#check tactic.interactive.clear
#check tactic.interactive.comp_val
#check tactic.interactive.congr
#check tactic.interactive.congr'
#check tactic.interactive.constructor
#check tactic.interactive.constructor_matching
#check tactic.interactive.continue
#check tactic.interactive.contradiction
#check tactic.interactive.conv
#check tactic.interactive.convert
#check tactic.interactive.delete_expr
#check tactic.interactive.delta
#check tactic.interactive.derive_functor
#check tactic.interactive.derive_lawful_functor
#check tactic.interactive.derive_lawful_traversable
#check tactic.interactive.derive_map_equations
#check tactic.interactive.derive_traverse
#check tactic.interactive.derive_traverse_equations
#check tactic.interactive.destruct
#check tactic.interactive.done
#check tactic.interactive.dsimp
#check tactic.interactive.dunfold
#check tactic.interactive.eapply
#check tactic.interactive.erewrite
#check tactic.interactive.exact
#check tactic.interactive.exactI
#check tactic.interactive.exacts
#check tactic.interactive.exfalso
#check tactic.interactive.existsi
#check tactic.interactive.ext
#check tactic.interactive.ext1
#check tactic.interactive.fail_if_success
#check tactic.interactive.fapply
#check tactic.interactive.field
-- No docstring
#check tactic.interactive.filter_instances
-- No docstring
#check tactic.interactive.find_lemma
-- No docstring
#check tactic.interactive.find_one_difference
-- No docstring
#check tactic.interactive.find_rule
-- No docstring
#check tactic.interactive.finish
-- No docstring
#check tactic.interactive.focus
--
#check tactic.interactive.fold_assoc
-- No docstring
#check tactic.interactive.fold_assoc1
-- No docstring
#check tactic.interactive.from
-- Same as `exact`
#check tactic.interactive.fsplit
-- No docstring
#check tactic.interactive.functor_derive_handler
--
#check tactic.interactive.functor_derive_handler'
--
#check tactic.interactive.funext
--
#check tactic.interactive.generalize
-- Make goal more general
#check tactic.interactive.generalize_a_aux
--
#check tactic.interactive.generalize_hyp
-- Similar to generalize
#check tactic.interactive.generalize_proofs
-- Similar to generalize?
#check tactic.interactive.get_current_field
-- No docstring
#check tactic.interactive.get_equations_of
-- No docstring
#check tactic.interactive.get_monotonicity_lemmas
-- No docstring
#check tactic.interactive.get_operator
-- No docstring
#check tactic.interactive.get_rule_eqn_lemmas
-- No docstring
#check tactic.interactive.guard_class
-- No docstring
#check tactic.interactive.guard_expr_eq
-- No docstring
#check tactic.interactive.guard_expr_eq'
-- No docstring
#check tactic.interactive.guard_hyp
-- Used for writing tests
#check tactic.interactive.guard_hyp'
-- Used for writing tests
#check tactic.interactive.guard_hyp_nums
-- No docstring
#check tactic.interactive.guard_tags
-- No docstring
#check tactic.interactive.guard_target
-- Used for writing tests
#check tactic.interactive.guard_target'
-- Used for writing tests
#check tactic.interactive.h_generalize
-- For dealing with casts and heterogenous equality
-- No docstring
#check tactic.interactive.has_to_tactic_format_mono_ctx
-- No docstring
#check tactic.interactive.has_to_tactic_format_mono_function
-- No docstring
#check tactic.interactive.has_to_tactic_format_mono_law
-- No docstring
#check tactic.interactive.have
--
#check tactic.interactive.haveI
--
#check tactic.interactive.have_field
--
#check tactic.interactive.hide_meta_vars'
-- No docstring
#check tactic.interactive.induction
--
#check tactic.interactive.injection
-- Apply the fact that constructors are injective.
#check tactic.interactive.injections
-- Apply the fact that constructors are injective, repeatedly
#check tactic.interactive.injections_and_clear
-- No docstring
#check tactic.interactive.intro
--
#check tactic.interactive.introI
--
#check tactic.interactive.intros
--
#check tactic.interactive.introsI
--
#check tactic.interactive.introv
-- Like intros, but with slightly different behaviour wrt naming
-- of introduced variables
#check tactic.interactive.iterate
-- Apply a tactic repeatedly
#check tactic.interactive.left
--
#check tactic.interactive.let
--
#check tactic.interactive.letI
#check tactic.interactive.list.minimum_on
-- No docstring
#check tactic.interactive.list_cast_of
-- No docstring
#check tactic.interactive.map_constructor
--
#check tactic.interactive.map_field
--
#check tactic.interactive.mapply
--
#check tactic.interactive.match_ac
-- No docstring
#check tactic.interactive.match_ac'
-- No docstring
#check tactic.interactive.match_ac'._main
-- No docstring
#check tactic.interactive.match_assoc
--
#check tactic.interactive.match_chaining_rules
-- No docstring
#check tactic.interactive.match_imp
-- No docstring
#check tactic.interactive.match_prefix
-- No docstring
#check tactic.interactive.match_rule
-- No docstring
#check tactic.interactive.match_target
-- Fail if type of target is not as specified
#check tactic.interactive.min_tac
-- No docstring
#check tactic.interactive.mk_congr_args
-- No docstring
#check tactic.interactive.mk_congr_law
-- No docstring
#check tactic.interactive.mk_fun_app
-- No docstring
#check tactic.interactive.mk_map
-- Helps to define functors.  You can just specify the effect on
-- objects and mk_map will try to work out the effect on maps (?)
#check tactic.interactive.mk_mapp'
-- No docstring
#check tactic.interactive.mk_one_instance
-- No docstring
#check tactic.interactive.mk_pattern
-- No docstring
#check tactic.interactive.mk_rel
-- No docstring
#check tactic.interactive.mk_traverse
-- Helps to define traversable functors
#check tactic.interactive.mono
-- Applies monotonicity rules
#check tactic.interactive.nested_map
--
#check tactic.interactive.nested_traverse
--
#check tactic.interactive.one_line
-- No docstring
#check tactic.interactive.rcases
--
#check tactic.interactive.refine
-- Like exact, but allows holes
#check tactic.interactive.refine_struct
-- When defining a structure, this tactic gives a goal for each required field
-- (Compare with hole commands?)
#check tactic.interactive.refl
--
#check tactic.interactive.reflexivity
-- Same as refl
#check tactic.interactive.rename
--
#check tactic.interactive.repeat
--
#check tactic.interactive.repeat_or_not
--
#check tactic.interactive.repeat_until
--
#check tactic.interactive.repeat_until_or_at_most
--
#check tactic.interactive.replace
--
#check tactic.interactive.resetI
--
#check tactic.interactive.revert
-- Reverse of intro
#check tactic.interactive.revert_all
--
#check tactic.interactive.rewrite
-- same as rw
#check tactic.interactive.right
-- If the goal is P ∨ Q, change it to Q.  Also works with other types with
-- precisely two constructors
#check tactic.interactive.rintro
--
#check tactic.interactive.rintros
-- Same as rintro
#check tactic.interactive.rsimp
-- No docstring
#check tactic.interactive.rw
--
#check tactic.interactive.rwa
-- rw followed by assumption
#check tactic.interactive.safe
--
#check tactic.interactive.same_function
-- No docstring
#check tactic.interactive.same_operator
-- No docstring
#check tactic.interactive.show
-- Similar to change, but can alo select a goal other than the first one
#check tactic.interactive.simp
--
#check tactic.interactive.simp_core
-- Is this the same as simp only[] ?
#check tactic.interactive.simp_functor
-- No docstring
#check tactic.interactive.simp_intros
#check tactic.interactive.simpa
-- Like intros, but with simplification
#check tactic.interactive.skip
-- Does nothing; only useful in combinators
#check tactic.interactive.solve1
--
#check tactic.interactive.solve_by_elim
--
#check tactic.interactive.solve_mvar
#check tactic.interactive.sorry
#check tactic.interactive.source_fields
#check tactic.interactive.specialize
#check tactic.interactive.split
#check tactic.interactive.split_ifs
#check tactic.interactive.squeeze_simpa
#check tactic.interactive.subst
#check tactic.interactive.subst_vars
#check tactic.interactive.substs
#check tactic.interactive.success_if_fail
#check tactic.interactive.suffices
#check tactic.interactive.swap
#check tactic.interactive.symmetry
#check tactic.interactive.tauto
#check tactic.interactive.tautology
#check tactic.interactive.to_expr'
#check tactic.interactive.trace
#check tactic.interactive.trace_simp_set
#check tactic.interactive.trace_state
#check tactic.interactive.transitivity
#check tactic.interactive.traversable_derive_handler
#check tactic.interactive.traversable_derive_handler'
#check tactic.interactive.traversable_law_starter
#check tactic.interactive.traverse_constructor
#check tactic.interactive.traverse_field
#check tactic.interactive.triv
#check tactic.interactive.trivial
#check tactic.interactive.try
#check tactic.interactive.try_for
#check tactic.interactive.type_check
#check tactic.interactive.unfold
#check tactic.interactive.unfold1
#check tactic.interactive.unfold_aux
#check tactic.interactive.unfold_coes
#check tactic.interactive.unfold_projs
#check tactic.interactive.unify_with_instance
#check tactic.interactive.use
#check tactic.interactive.with_cases
#check tactic.interactive.with_prefix
#check tactic.interactive.wlog


/-
Copyright 2020 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 -/
import measure_theory.measurable_space

import measure_theory.measure_space
import measure_theory.outer_measure
import measure_theory.lebesgue_measure
import measure_theory.integration

import measure_theory.borel_space
import data.set.countable
import formal_ml.nnreal
import formal_ml.sum
import formal_ml.lattice
import formal_ml.measurable_space
import formal_ml.classical
import data.equiv.list
import formal_ml.probability_space

/-! This file gives various ways to produce independent events. -/

--- Find a different place for these lemmas ----------
lemma disjoint_exists_and {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (x y:finset α):set.pairwise_on (↑(x∪ y)) (disjoint on (λ s, (E s).val)) →
  ((∃ᵣ a in x, E a) ∧ (∃ᵣ a in y, E a)) = (∃ᵣ a in (x ∩ y), E a) :=
begin
  classical,
  intros h_disj,
  apply event.eq,
  ext1 ω, split; intros h1; simp at h1; simp,
  { cases h1 with h1 h2,
    cases h1 with a h1,
    cases h2 with a' h2,
    have h3:a = a',
    { apply by_contradiction, intros h_contra,
      have h3_1:= h_disj a _ a' _ h_contra,
      rw [function.on_fun, disjoint_iff, subtype.val_eq_coe, set.inf_eq_inter, set.bot_eq_empty,
          ← set.subset_compl_iff_disjoint,
          set.subset_def] at h3_1,
      apply h3_1 ω h1.right h2.right,
      simp [h1],
      simp [h2] },
    subst a', apply exists.intro a,
    simp [h1, h2] },
  { cases h1 with a h1,
    split; apply exists.intro a; simp [h1] },
end

lemma disjoint_exists_diff {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (x y:finset α):set.pairwise_on (↑(x∪ y)) (disjoint on (λ s, (E s).val)) →
  ((∃ᵣ a in x, E a) \ (∃ᵣ a in y, E a)) = (∃ᵣ a in (x \ y), E a) :=
begin
  classical,
  intros h_disj,
  apply event.eq,
  ext1 ω, split; intros h1; simp at h1; simp,
  { cases h1 with h1 h2,
    cases h1 with i h1,
    apply exists.intro i,
    split, split,
    { apply h1.left },
    { intros contra, apply h2 i contra,
      apply h1.right },
    apply h1.right },
  { cases h1 with i h1,
    split,
    { apply exists.intro i, simp [h1] },
    { intros a' h_a'_in_y h_contra,
      have h2:a' = i,
      { apply by_contradiction,
        intros h_contra2,
        have h2_disj := h_disj a' _ i _ h_contra2,
        rw [function.on_fun, set.disjoint_left] at h2_disj, simp at h2_disj,
        apply h2_disj h_contra,
        apply h1.right,
        simp,
        right,
        apply h_a'_in_y,
        simp,
        left,
        apply h1.left.left },
      subst a',
      apply h1.left.right,
      apply h_a'_in_y }  },
end

lemma exists_or {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (x y:finset α):
  ((∃ᵣ a in x, E a) ∨ (∃ᵣ a in y, E a)) = (∃ᵣ a in (x ∪ y), E a) :=
begin
  classical,
  apply event.eq,
  ext1 ω, split; intros h1; simp at h1; simp,
  { cases h1 with h1 h1;
    { cases h1 with a h1,
      apply exists.intro a, simp [h1] } },
  { cases h1 with a h1,
    cases h1 with h1 h2,
    cases h1 with h1 h1,
    { left, apply exists.intro a, simp [h1,h2] },
    { right, apply exists.intro a, simp [h1,h2] } },
end

lemma forall_and {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (x y:finset α):
  ((∀ᵣ a in x, E a) ∧ (∀ᵣ a in y, E a)) = (∀ᵣ a in (x ∪ y), E a) :=
begin
  classical,
  apply event.eq,
  ext1 ω, split; intros h1; simp at h1; simp [h1],
  { cases h1 with h1 h2,
    intros a h3,
    cases h3 with h3 h3,
    { apply h1 a h3 },
    { apply h2 a h3 } },
  { split; intros a h2; apply h1;
    { simp [h2] } },
end

-----------------------------------------

lemma independent_event_pair_exists {Ω α:Type*} {P:probability_space Ω}
 {S:finset α} {E:event P} {F:α → event P} [decidable_eq (event P)]:
  (∀ (s∈ S), independent_event_pair E (F s)) →
  (set.pairwise_on (↑S) (disjoint on (λ a, (F a).val))) →
  independent_event_pair E (∃ᵣ a in S, F a) := begin
  classical,
  intros h1 h2,
  simp [independent_event_pair],
  --rw eany_in_finset_def,
  rw Pr_sum_disjoint_eq',

  rw ← finset.sum_distrib_left,
  rw ← distrib_exists_and,
  rw Pr_sum_disjoint_eq',
  apply finset.sum_congr,
  { refl },
  { intros x h_x, have h4 := h1 x h_x, simp [independent_event_pair] at h4,
    apply h4 },
  { intros i h_i j h_j h_ne, simp only [function.on_fun], rw disjoint_iff,
    simp, rw ← set.subset_empty_iff, apply set.subset.trans,
    apply set.inter_subset_inter,
    apply set.inter_subset_right,
    apply set.inter_subset_right,
    have h5 := h2 i _ j _ h_ne, simp only [function.on_fun] at h5, rw disjoint_iff at h5,
    simp at h5, rw h5, apply h_i, apply h_j },
  apply h2,
end

lemma independent_events_induction {Ω:Type*} {α:Type*} {P:probability_space Ω}
  {E:α → event P}:(∀ (a:α) (S:finset α), (a ∉ S) →
   independent_event_pair (E a) (∀ᵣ a' in S, E a')) →
  (independent_events E) :=
begin
  classical,
  intros h1 T,
  apply finset.induction_on T,
  { simp },
  { intros a s h2 h3,
    rw finset.prod_insert,
    rw eall_finset_insert,
    have h4 := h1 a s h2,
    unfold independent_event_pair at h4,
    rw h4,
    rw ← h3,
    apply h2 },
end

@[simp]
lemma event_univ_and {Ω:Type*} {P:probability_space Ω} {A:event P}:
(event_univ ∧ A) = A := begin
  apply event.eq, simp,
end

lemma Pr_and_or_not_and {Ω:Type*} {P:probability_space Ω}  {A B:event P}:
Pr[(A ∧ B)] + Pr[(¬ₑ A) ∧ B] = Pr[B] := begin
  have h1:((A ∧ B) ∨ ((¬ₑ A)) ∧ B) = B,
  { apply event.eq, ext ω, split; intros h1; simp at h1; simp [h1],
    cases h1 with h1 h1; simp [h1], apply (classical.em  (ω ∈ ↑(A))), },
  rw ← Pr_disjoint_eor,
  rw h1,
  rw disjoint_iff,
  ext1 ω, split; intros h2; simp at h2; simp [h2],
  apply h2.right.left h2.left.left,
  apply false.elim h2,
end

lemma Pr_not_and_eq {Ω:Type*} {P:probability_space Ω}  {A B:event P}:
  Pr[(¬ₑ A)∧ B] = Pr[B] - Pr[A ∧ B] :=
begin
  rw ← @Pr_and_or_not_and _ _ A B,
  rw add_comm,
  rw nnreal.add_sub_cancel,
end

lemma nnreal.sub_mul_eq {a b c:nnreal}:(a - b) * c = (a * c) - (b * c) :=
begin
  cases (le_total a b) with h h,
  { have h2:(a * c) ≤ (b * c),
    { apply mul_le_mul', apply h, apply le_refl _ },
    rw nnreal.sub_eq_zero h,
    rw nnreal.sub_eq_zero h2,
    simp },
  have h2:(b * c) ≤ (a * c),
  { apply mul_le_mul', apply h, apply le_refl _ },
  rw ←  nnreal.coe_eq,
  rw nnreal.coe_mul,
  rw nnreal.coe_sub h,
  rw nnreal.coe_sub h2,
  rw nnreal.coe_mul,
  rw nnreal.coe_mul,
  linarith,
end

lemma independent_events_rel {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (f:α → event P)
(h_ind:independent_events f) (T_not:finset α) (T_same:finset α) (h_disj:disjoint T_not T_same):
Pr[(∀ᵣ a in T_not, ¬ₑ (f a)) ∧ (∀ᵣ a in T_same, (f a))]
  = (T_not.prod (λ a, Pr[¬ₑ (f a)])) * (T_same.prod (λ a, Pr[f a])) :=
begin
  revert T_same,
  apply finset.induction_on T_not,
  { intros T_same h_disj, simp, rw h_ind, },
  { intros a s h_a_notin_s h_ind T_same T_disj,
    rw has_eall_in_insert, rw eand_assoc,
    rw Pr_not_and_eq,
    rw h_ind T_same _,
    have h1:∀ (A B C:event P), (A ∧ (B ∧ C)) = (B ∧ (A ∧ C)),
    { intros A B C, apply event.eq, ext1 ω; split; intros h1; simp at h1; simp [h1] },
    rw ←  eand_assoc,
    rw eand_comm (f a),
    rw eand_assoc,
    rw ← has_eall_in_insert,
    rw h_ind (insert a T_same) _,
    rw finset.prod_insert,
    rw finset.prod_insert,
    rw ← Pr_one_minus_eq_not,
    rw nnreal.sub_mul_eq,
    rw nnreal.sub_mul_eq,
    rw ← mul_assoc,
    rw mul_comm _ (Pr[f a]),
    simp,
    { apply h_a_notin_s },
    { rw finset.disjoint_left at T_disj,
      apply T_disj,
      simp },
    { rw finset.disjoint_left,
      intros a' h_a'_in_s,
      rw finset.disjoint_left at T_disj,
      intros contra,
      simp at contra,
      cases contra with contra contra,
      { subst a', apply h_a_notin_s h_a'_in_s },
      { apply @T_disj a', simp [h_a'_in_s], apply contra,  } },
    { rw finset.disjoint_left, intros a' h_a'_in_s,
      rw finset.disjoint_left at T_disj,
      apply T_disj,
      simp [h_a'_in_s] } },
end

/-- This represents a function of a finite number of events in a tabular way. -/
def function_of_events {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α) (F:event P):Prop := ∃ (T:finset (finset α)), T ⊆ S.powerset ∧
  (∃ᵣ s in T,  (∀ᵣ a in s, E a) ∧ (∀ᵣ a in (S \ s), ¬ₑ(E a))) = F

lemma function_of_events_event {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α) (a:α) (h_a:a ∈ S):function_of_events E S (E a) := begin
  classical,
  apply exists.intro (S.powerset.filter (λ s, a ∈ s)),
  split,
  { simp },
  apply event.eq,
  ext1 ω, split; intros h1; simp [h1]; simp at h1,
  { cases h1 with s h1,
    cases h1 with h1 h2, cases h2 with h2 h3,
    apply h2 a h1.right, },
  { apply exists.intro (S.filter (λ a', ω ∈ E a')),
    simp [h_a, h1],split,
    { apply h1 },
    split,
    { intros i h4 h5, apply h5 },
    { intros i h4 h5, apply h5 h4 } },
end

lemma disjoint_union {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α):set.pairwise_on ↑(S.powerset) (λ s t, disjoint
  ((∀ᵣ a in s, E a) ∧ (∀ᵣ a in (S \ s), ¬ₑ(E a))).val
  ((∀ᵣ a in t, E a) ∧ (∀ᵣ a in (S \ t), ¬ₑ(E a))).val) := begin
  classical,
  intros i h_i j h_j h_ne,
  have h_ne_exists:∃ a, ((a ∈ i) ∧ (a ∉ j)) ∨ ((a∉ i) ∧ (a ∈ j)),
  { rw ← not_forall_not, intros contra, apply h_ne,
    ext a, have contra_a := contra a,
    split; intros h_1;
    apply by_contradiction; intros h_2;
    apply contra_a;
    simp [contra_a, h_1, h_2] },
  have h_i_subset_S:i ⊆ S,
  { rw [finset.mem_coe, finset.mem_powerset] at h_i, apply h_i },
  have h_j_subset_S:j ⊆ S,
  { rw [finset.mem_coe, finset.mem_powerset] at h_j, apply h_j },
  rw disjoint_iff,
  simp,
  rw ← set.subset_compl_iff_disjoint,
  rw set.subset_def,
  intros ω h_ω,
  simp at h_ω,
  simp,
  intros h_j,
  cases h_ne_exists with a h_ne_exists,
  cases h_ne_exists with h_a_in_i h_a_in_j,
  { have h_ω_2 := h_ω.left a h_a_in_i.left,
    apply exists.intro a,
    simp [h_ω_2, h_a_in_i],
    apply h_i_subset_S,
    apply h_a_in_i.left  },
  { exfalso, apply h_ω.right a _ h_a_in_j.left,
    apply h_j, apply h_a_in_j.right,
    apply h_j_subset_S,
    apply h_a_in_j.right },
end

lemma disjoint_union_sub {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α) (F:finset (finset α)) (h_sub:F⊆ S.powerset):set.pairwise_on ↑(F) (λ s t, disjoint
  ((∀ᵣ a in s, E a) ∧ (∀ᵣ a in (S \ s), ¬ₑ(E a))).val
  ((∀ᵣ a in t, E a) ∧ (∀ᵣ a in (S \ t), ¬ₑ(E a))).val) := begin
  apply @set.pairwise_on.mono (finset α) (↑S.powerset) ↑F
    (λ (s t : finset α),
       disjoint ((∀ᵣ (a : α) in s,E a)∧∀ᵣ (a : α) in S \ s,¬ₑ E a).val
         ((∀ᵣ (a : α) in t,E a)∧∀ᵣ (a : α) in S \ t,¬ₑ E a).val)
  _ _,
  { simp [h_sub] },
  apply disjoint_union,
end

#print instances has_sdiff

lemma function_of_events_diff {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α) (F G:event P) (h_F:function_of_events E S F)
  (h_G:function_of_events E S G):function_of_events E S (F \ G) := begin
  cases h_F with F_T h_F,
  cases h_G with G_T h_G,
  apply exists.intro (F_T \ G_T),
  split,
  { apply finset.subset.trans, apply finset.sdiff_subset, apply h_F.left },
  rw ← disjoint_exists_diff,
  rw h_F.right,
  rw h_G.right,
  apply disjoint_union_sub,
  apply finset.union_subset,
  apply h_F.left,
  apply h_G.left,
end

@[simp]
lemma function_of_events_univ {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α): (function_of_events E S event_univ) := begin
  classical,
  apply exists.intro (S.powerset),
  split,
  apply finset.subset.refl,
  apply event.eq,
  ext1 ω, split; intros h1; simp at h1, simp,
  apply exists.intro (S.filter (λ a, ω ∈ (E a))),
  split,
  simp,
  split,
  { intros i h_i,
    simp at h_i,
    apply h_i.right },
  { intros i h_1 h_2 h_3, apply h_2,
    simp,
    apply and.intro h_1,
    apply h_3 },
end

lemma function_of_events_compl {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α) (F:event P) (h_F:function_of_events E S F):function_of_events E S (Fᶜ) := begin
  have h1:event_univ \ F = Fᶜ,
  { apply event.eq, ext1 ω, split; intros h1; simp at h1; simp [h1], },
  rw ← h1,
  apply function_of_events_diff,
  simp,
  apply h_F,
end

lemma function_of_events_and {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α) (F G:event P) (h_F:function_of_events E S F)
  (h_G:function_of_events E S G):function_of_events E S (F ∧ G) := begin
  have h1:(F ∧ G) = F \ Gᶜ,
  { apply event.eq, ext1 ω, split; intros h1; simp at h1; simp [h1], },
  rw h1,
  apply function_of_events_diff,
  apply h_F,
  apply function_of_events_compl,
  apply h_G,
end

lemma function_of_events_or {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α) (F G:event P) (h_F:function_of_events E S F)
  (h_G:function_of_events E S G):function_of_events E S (F ∨ G) := begin
  cases h_F with F_T h_F,
  cases h_G with G_T h_G,
  apply exists.intro (F_T ∪ G_T),
  split,
  { apply finset.union_subset; simp [h_F, h_G], },
  rw ← exists_or,
  rw h_F.right,
  rw h_G.right,
end

lemma function_of_events_not {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α) (F:event P) (h_F:function_of_events E S F):function_of_events E S (¬ₑ F) := begin
  have h1:(¬ₑ F) = Fᶜ,
  { apply event.eq, simp },
  rw h1,
  apply function_of_events_compl,
  apply h_F,
end

lemma function_of_events_empty {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α):function_of_events E S ∅ := begin
  have h1:(¬ₑ event_univ) = (∅:event P),
  { apply event.eq, simp },
  rw ← h1,
  apply function_of_events_compl,
  apply function_of_events_univ,
end

lemma function_of_events_forall {α β Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (S:finset α) (T:finset β) (F:β → event P) (h_F:∀ b∈ T, function_of_events E S (F b))
  :function_of_events E S (∀ᵣ b in T, F b) := begin
  classical,
  revert h_F,
  apply finset.induction_on T,
  { intros, simp },
  { intros b T' h_b_notin_T' h2 h3, rw has_eall_in_insert,
    apply function_of_events_and, { apply h3, simp },
    { apply h2, intros b h_b,
      apply h3, simp [h_b] } },
end

lemma function_of_events_ind {α Ω:Type*} {P:probability_space Ω} [decidable_eq α] (E:α → event P)
  (h_ind:independent_events E) (S T:finset α) (F G:event P) (h_disj: disjoint S T)
  (h_F:function_of_events E S F) (h_G:function_of_events E T G):
  independent_event_pair F G :=
begin
  classical,
  have h_disj_diff:∀ (s t:finset α), disjoint (s \ t) t,
  { intros s t, rw finset.disjoint_left,
    intros a h_a, simp at h_a, apply h_a.right },

  have h_disj_subset:∀ (s t:finset α), (s ⊆ S) → (t ⊆ T) → (disjoint s t),
  { intros s t h_s h_t,
    rw finset.disjoint_left,
    rw finset.disjoint_left at h_disj,
    intros a h_a h_a',
    apply h_disj,
    apply h_s h_a,
    apply h_t h_a' },
  cases h_G with T_G h_G,
  have h_subset_T:∀ t ∈ T_G, t ⊆ T,
  { intros t h_t,
    apply finset.mem_powerset.1 (h_G.left h_t) },
  cases h_F with T_F h_F,
  have h_subset_S:∀ s ∈ T_F, s ⊆ S,
  { intros s h_s,
    apply finset.mem_powerset.1 (h_F.left h_s) },
  rw ← h_G.right,
  apply independent_event_pair_exists,
  intros s h_s,
  rw ← h_F.right,
  apply independent_event_pair.symm,
  apply independent_event_pair_exists,
  intros t h_t,
  apply independent_event_pair.symm,
  rw eand_comm,
  rw eand_comm (∀ᵣ (a : α) in s,E a),
  unfold independent_event_pair,
  have h1:∀ (H1 H2 H3 H4:event P), ((H1 ∧ H2) ∧ (H3 ∧ H4)) = ((H1 ∧ H3) ∧ (H2 ∧ H4)),
  { intros H1 H2 H3 H4,
    apply event.eq, ext1 ω, split; intros h1_1; simp at h1_1; simp [h1_1] },
  rw h1,
  rw forall_and,
  rw forall_and,
  rw independent_events_rel,
  rw independent_events_rel,
  rw independent_events_rel,
  rw finset.prod_union,
  rw finset.prod_union,
  have h2:∀ (a b c d:nnreal), a * b * (c * d) = a * c * (b * d),
  { intros a b c d, rw ← nnreal.coe_eq, repeat { rw nnreal.coe_mul },
    linarith },
  rw h2,
  { apply h_disj_subset,
    apply h_subset_S _ h_t,
    apply h_subset_T _ h_s },
  { apply h_disj_subset,
    simp,
    simp },
  apply h_ind,
  { rw finset.disjoint_left,
    intros a h_a, simp at h_a, apply h_a.right },
  apply h_ind,
  { rw finset.disjoint_left,
    intros a h_a, simp at h_a, apply h_a.right },
  apply h_ind,
  { rw finset.disjoint_left,
    intros a h_a h_a',  simp at h_a', simp [h_a'] at h_a,
    cases h_a with h_a h_a; cases h_a' with h_a' h_a',
    { apply h_a.right h_a' },
    { rw finset.disjoint_left at h_disj,
     apply h_disj h_a.left,
     apply h_subset_T s h_s h_a' },
    { rw finset.disjoint_right at h_disj,
      apply h_disj h_a.left,
      apply h_subset_S t h_t h_a' },
    { apply h_a.right h_a' } },
  { apply disjoint_union_sub, apply h_F.left },
  { apply disjoint_union_sub, apply h_G.left },
end


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,
  assume d hd,
  have h1 : (d + 1) * (1 + x)^d - (d + 1) * x^d =
            (d + 1) * (1 + x)^d - (d + 1) * x^d,
  {
    rw add_comm,
    ring,
  },
  have h2 : (d + 1) * (1 + x)^d - (d + 1) * x^d =
            (d + 1) * ((1 + x)^d - x^d),
  {
    ring,
  },
  rw h2,
  clear h2,
  have h3 : (1 + x)^d = x^d + d * x^(d - 1) + (↑d : ℚ) * x^d,
  {
    rw hd,
    ring,
  },
  rw h3,
  have h4 : (d + 1) * ((1 + x)^d - x^d) = (d + 1) * (x^d + d * x^(d - 1) + (↑d : ℚ) * x^d - x^d),
  {
    ring,
  },
  rw h4,
  clear h4,
  rw ←polynomial.eval_sum,
  {
    have h5 : (d + 1) * (x^d + d * x^(d - 1) + (↑d : ℚ) * x^d - x^d) =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h5,
    clear h5,
    have h6 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h6,
    clear h6,
    have h7 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h7,
    clear h7,
    have h8 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h8,
    clear h8,
    have h9 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h9,
    clear h9,
    have h10 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h10,
    clear h10,
    have h11 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h11,
    clear h11,
    have h12 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h12,
    clear h12,
    have h13 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h13,
    clear h13,
    have h14 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h14,
    clear h14,
    have h15 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h15,
    clear h15,
    have h16 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d,
    {
      ring,
    },
    rw h16,
    clear h16,
    have h17 : (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (d + 1) * x^d =
              (d + 1) * (x^d + d * x^(d - 1)) + (d + 1) * (↑d : ℚ) * x^d - (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
  have h1 : ∀ m : ℕ, m < n → (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from
    assume (m : ℕ) (hm : m < n),
    begin
      induction m with hm hih,
      { rw [polynomial.bernoulli,eval_C,add_zero,add_zero], },
      { rw [polynomial.bernoulli,eval_C,add_zero,add_zero],
        exact hih hm,
      }
    end,
  have h2 : ∀ l : ℕ, (polynomial.bernoulli l).eval 1 = l, from
    assume (l : ℕ), (polynomial.bernoulli l).eval 1,
  have h3 : ∀ l : ℕ, (polynomial.bernoulli l).eval x = 0, from
    assume (l : ℕ), (polynomial.bernoulli l).eval x,
  have h4 : ∀ l : ℕ, (polynomial.bernoulli l).eval (1 + x) = (polynomial.bernoulli l).eval 1 + (polynomial.bernoulli l).eval x, from
    assume (l : ℕ), (polynomial.bernoulli l).eval (1 + x),
  have h5 : ∀ l : ℕ, (polynomial.bernoulli l).eval (1 + x) = (polynomial.bernoulli l).eval 1, from
    assume (l : ℕ), eq.trans (h4 l) (add_zero (polynomial.bernoulli l).eval 1),
  have h6 : ∀ l : ℕ, (polynomial.bernoulli l).eval (1 + x) = l, from
    assume (l : ℕ), eq.trans (h5 l) (h2 l),
  have h7 : ∀ l : ℕ, (polynomial.bernoulli l).eval (1 + x) = (polynomial.bernoulli l).eval x, from
    assume (l : ℕ), eq.trans (h4 l) (add_zero (polynomial.bernoulli l).eval x),
  have h8 : ∀ l : ℕ, (polynomial.bernoulli l).eval (1 + x) = 0, from
    assume (l : ℕ), eq.trans (h7 l) (h3 l),
  have h9 : ∀ l : ℕ, (polynomial.bernoulli l).eval (1 + x) = (polynomial.bernoulli l).eval x + l * x^(l - 1), from
    assume (l : ℕ), eq.trans (h4 l) (add_mul  (polynomial.bernoulli l).eval 1 x (l - 1)),
  have h10 : ∀ l : ℕ, (polynomial.bernoulli l).eval (1 + x) = l * x^(l - 1), from
    assume (l : ℕ), eq.trans (h9 l) (add_right_cancel (polynomial.bernoulli l).eval x),
  have h11 : ∀ l : ℕ, (polynomial.bernoulli l).eval (1 + x) = l * x^(l - 1), from
    assume (l : ℕ), by {rw [← h6 l,← h8 l], ring},

  have h12 : (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1), from
    by {rw [← h11 n, ← mul_one (polynomial.bernoulli n).eval (1 + x), ← add_mul (polynomial.bernoulli n).eval 1 x (n - 1)], ring},
  show (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1), from
    by {rw [← h4 n, ← add_mul (polynomial.bernoulli n).eval 1 x (n - 1)], ring},
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
  -- use strong induction
  apply nat.strong_induction_on n,
  assume (d : ℕ) (ih : ∀ m : ℕ, m < d → (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1)),
  have h1 : ∀ l : ℕ, (d + 1) * (1 + x)^l - (d + 1) * x^l = ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k d) * x^k, from assume (l : ℕ),
    calc
    (d + 1) * (1 + x)^l - (d + 1) * x^l = (d + 1) * ∑ k in finset.range (l + 1), (finset.nat_fintype.choose k l) * x^k : by rw polynomial.eval_sum (1 + x) l
    ... = ∑ k in finset.range (l + 1), (d + 1) * (finset.nat_fintype.choose k l) * x^k : by rw ← finset.sum_mul_distrib
    ... = ∑ k in finset.range (l + 1), (finset.nat_fintype.choose k l) * ((d + 1) * x^k) : by rw finset.sum_mul_distrib
    ... = ∑ k in finset.range (l + 1), (finset.nat_fintype.choose k d) * ((d + 1) * x^k) : by {rw finset.nat_fintype.choose_succ, rw finset.nat_fintype.choose_zero},
  have h2 : ∀ l : ℕ, ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k d) * x^k = ∑ k in finset.range (l + 1), (l + 1) * k * (finset.nat_fintype.choose k (d - 1)) * x^(k - 1), from assume (l : ℕ),
    calc
    ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k d) * x^k = ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k d) * x^(k - 1 + 1) : by rw finset.sum_mul_distrib
    ... = ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k d) * x^(k - 1) * x : by rw finset.sum_mul_distrib
    ... = ∑ k in finset.range (l + 1), (l + 1) * x^(k - 1) * (finset.nat_fintype.choose k d) * x : by rw finset.sum_mul_distrib
    ... = ∑ k in finset.range (l + 1), (l + 1) * x^(k - 1) * (finset.nat_fintype.choose (k - 1) d) * x : by rw finset.nat_fintype.choose_succ
    ... = ∑ k in finset.range (l + 1), (l + 1) * x^(k - 1) * (finset.nat_fintype.choose (k - 1) (d - 1)) * x : by rw finset.nat_fintype.choose_succ
    ... = ∑ k in finset.range (l + 1), (l + 1) * k * (finset.nat_fintype.choose (k - 1) (d - 1)) * x^(k - 1) : by rw finset.nat_fintype.choose_zero,
  have h3 : ∀ l : ℕ, ∑ k in finset.range (l + 1), (l + 1) * k * (finset.nat_fintype.choose k (d - 1)) * x^(k - 1) = ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k (d - 1)) * (k * x^(k - 1)), from assume (l : ℕ),
    calc
    ∑ k in finset.range (l + 1), (l + 1) * k * (finset.nat_fintype.choose k (d - 1)) * x^(k - 1) = ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k (d - 1)) * (k * x^(k - 1)) : by rw finset.sum_mul_distrib,
  have h4 : ∀ l : ℕ, ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k (d - 1)) * (k * x^(k - 1)) = ∑ k in finset.range (l + 1), (l + 1) * k * (finset.nat_fintype.choose k (d - 1)) * x^(k - 1), from assume (l : ℕ),
    calc
    ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k (d - 1)) * (k * x^(k - 1)) = ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k (d - 1)) * x^(k - 1) * k : by rw finset.sum_mul_distrib
    ... = ∑ k in finset.range (l + 1), (l + 1) * k * (finset.nat_fintype.choose k (d - 1)) * x^(k - 1) : by rw finset.sum_mul_distrib,
  have h5 : ∀ l : ℕ, ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k (d - 1)) * (k * x^(k - 1)) = ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k (d - 1)) * ((k - 1 + 1) * x^(k - 1)), from assume (l : ℕ),
    calc
    ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k (d - 1)) * (k * x^(k - 1)) = ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k (d - 1)) * ((k - 1) * x^(k - 1)) + (l + 1) * (finset.nat_fintype.choose k (d - 1)) * x^(k - 1) : by rw finset.sum_distrib
    ... = ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k (d - 1)) * ((k - 1) * x^(k - 1)) + (l + 1) * (finset.nat_fintype.choose (k - 1) (d - 1)) * x^(k - 1) : by rw finset.nat_fintype.choose_succ
    ... = ∑ k in finset.range (l + 1), (l + 1) * (finset.nat_fintype.choose k (d - 1)) * ((k - 1) * x^(
end --Needs more than 2000 tokens!

--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
  -- strong induction
  have h : ∀ n : ℕ, ∀ x : ℚ, n < (polynomial.bernoulli n).nat_degree →
    (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1), from
      assume (n : ℕ) (x : ℚ) (h : n < (polynomial.bernoulli n).nat_degree),
      have h1 : ∀ m < n, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from
          assume (m : ℕ) (h : m < n), show (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from
          begin
            induction m with m hm,
            show (polynomial.bernoulli 0).eval (1 + x) = (polynomial.bernoulli 0).eval x + 0 * x^(0 - 1), from rfl,
            show (polynomial.bernoulli (nat.succ m)).eval (1 + x) = (polynomial.bernoulli (nat.succ m)).eval x + (nat.succ m) * x^(nat.succ m - 1), from
            begin
              rw [polynomial.bernoulli, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add],
              rw [polynomial.eval_C, polynomial.eval_C, polynomial.eval_C, polynomial.eval_C, polynomial.eval_C, polynomial.eval_C, polynomial.eval_C, polynomial.eval_C, polynomial.eval_C, polynomial.eval_C, polynomial.eval_C, polynomial.eval_C, polynomial.eval_C],
              rw [polynomial.eval_X, polynomial.eval_X, polynomial.eval_X, polynomial.eval_X, polynomial.eval_X, polynomial.eval_X, polynomial.eval_X, polynomial.eval_X, polynomial.eval_X, polynomial.eval_X, polynomial.eval_X, polynomial.eval_X, polynomial.eval_X],
              rw [polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul],
              rw [polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add, polynomial.eval_add],
              rw [polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul],
              rw [polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul],
              rw [polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul],
              rw [polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul],
              rw [polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul],
              rw [polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul],
              rw [polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul],
              rw [polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul],
              rw [polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul, polynomial.eval_mul],
              rw [polynomial.eval_mul, polynomial.eval_mul, polynomial.eval
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
  -- 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}$$
  induction n with d hd,
  { -- base case
    assume x : ℚ,
    calc (polynomial.bernoulli 0).eval (1 + x) = ((polynomial.bernoulli 0).eval 1) + x : by {
      have h1 : ∀ x : ℚ, (polynomial.bernoulli 0).eval (1 + x) = (polynomial.bernoulli 0).eval 1 + x, from by {
        assume x : ℚ,
        rw [polynomial.bernoulli,eval_add,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_C,eval_X,eval_
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
  -- 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)`
  assume h1 : ∀ (m : ℕ) (x : ℚ), m < d → (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1),
  -- and we want to show that `B_{d} (1 + x) = B_{d} (x) + d * x^(d - 1)`
  have h2 : (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + d * x^(d - 1),
  -- Multiplying both sides by `d + 1`, and using the fact that, for all `l` in `ℕ`, `∑_{k = 0}^{l} {l + 1 choose k} * (polynomial.bernoulli k) = (l + 1) * X^l` (where `B_k` is the $k$-th Bernoulli number),
  begin
    -- we get that
    calc (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + d * x^(d - 1) :
    -- $$ (d + 1) (1 + x)^d - (d + 1) x^d = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
    begin
      -- we get that
      have h3 : (d + 1) * (1 + x)^d - (d + 1) * x^d = ∑ (k : ℕ) in finset.range (d + 1), (d + 1) * (polynomial.choose_nat (d + 1) k) * (polynomial.bernoulli k).eval x, from
        begin
          -- we get that
          have h31 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (((1 + x)^d - x^d) : polynomial ℚ), from
            by { rw [← polynomial.coeff_mul_X_pow, polynomial.coeff_sub (d + 1)], ring },
          -- we get that
          have h32 : (1 + x)^d - x^d = polynomial.coeff_mul_X_pow (d + 1) (∑ (k : ℕ) in finset.range (d + 1), (polynomial.choose_nat (d + 1) k) * (polynomial.bernoulli k) : polynomial ℚ), from
            begin
              -- we get that
              have h321 : (1 + x)^d - x^d = polynomial.coeff_mul_X_pow (d + 1) (∑ (k : ℕ) in finset.range (d + 1), (polynomial.choose_nat (d + 1) k) * (polynomial.bernoulli k : polynomial ℚ)), from
                by { apply polynomial.coeff_sub (d + 1), simp [polynomial.coeff_mul_X_pow],
                rw [← polynomial.coeff_mul_X_pow, polynomial.coeff_sub (d + 1)], ring },
              -- we get that
              have h322 : (1 + x)^d - x^d = polynomial.coeff_mul_X_pow (d + 1) (∑ (k : ℕ) in finset.range (d + 1), (polynomial.choose_nat (d + 1) k) * (polynomial.bernoulli k) : polynomial ℚ), from
                begin
                  -- we get that
                  have h3221 : (1 + x)^d - x^d = polynomial.coeff_mul_X_pow (d + 1) (∑ (k : ℕ) in finset.range (d + 1), (polynomial.choose_nat (d + 1) k) * (polynomial.bernoulli k : polynomial ℚ)), from
                    by { apply polynomial.coeff_sub (d + 1), simp [polynomial.coeff_mul_X_pow],
                    rw [← polynomial.coeff_mul_X_pow, polynomial.coeff_sub (d + 1)], ring },
                  -- we get that
                  have h3222 : (1 + x)^d - x^d = polynomial.coeff_mul_X_pow (d + 1) (∑ (k : ℕ) in finset.range (d + 1), (polynomial.choose_nat (d + 1) k) * (polynomial.bernoulli k) : polynomial ℚ), from
                    begin
                      -- we get that
                      have h32221 : (1 + x)^d - x^d = polynomial.coeff_mul_X_pow (d + 1) (∑ (k : ℕ) in finset.range (d + 1), (polynomial.choose_nat (d + 1) k) * (polynomial.bernoulli k) : polynomial ℚ), from
                        by { apply polynomial.coeff_sub (d + 1), simp [polynomial.coeff_mul_X_pow],
                        rw [← polynomial.coeff_mul_X_pow, polynomial.coeff_sub (d + 1)], ring },
                      -- we get that
                      have h32222 : (1 + x)^d - x^d = polynomial.coeff_mul_X_pow (d + 1) (∑ (k : ℕ) in finset.range (d + 1), (polynomial.choose_nat (d + 1) k) * (polynomial.bernoulli k) : polynomial ℚ), from
                        begin
                          -- we get that
                          have h322221 : (1 + x)^d - x^d = polynomial.coeff_mul_X_pow (d + 1) (∑ (k : ℕ) in finset.range (d + 1), (polynomial.choose_nat (d + 1) k) * (polynomial.bernoulli k) : polynomial ℚ), from
                            by { apply polynomial.coeff_sub (d + 1), simp [polynomial.coeff_mul_X_pow],
                            rw [← polynomial.coeff_mul_X_pow, polynomial.coeff_sub (d + 1)], ring },
                          -- we get that
                          have h322222 : (1 + x)^d - x^d = polynomial.coeff_mul_X_pow (d + 1) (∑ (k : ℕ) in finset.range (d + 1), (polynomial.choose_nat (d + 1) k) * (polynomial.bernoulli k) : polynomial ℚ), from
                            begin
                              -- we get that
                              have h3222221 : (1 + x)^d - x^d = polynomial.coeff_mul_X_pow (d + 1) (∑ (k : ℕ) in finset.range (d + 1), (polynomial.choose_nat (d + 1) k) * (polynomial.bernoulli k) : polynomial ℚ), from
                                by { apply polynomial.coeff_sub (d + 1), simp [polynomial.coeff_mul_X_pow],
                                rw [← polynomial.coeff_mul_X_pow, po
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
  -- Applying strong induction on $n$
  induction n with d hd,
  -- For $n = 0$, the conclusion is trivial.
  show (polynomial.bernoulli 0).eval (1 + x) = (polynomial.bernoulli 0).eval x + 0 * x^(0 - 1),
  from by obviously,

  -- 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 hd : ∀ m < d, (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1),
  have h1 : (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + d * x^(d - 1), from by {
    -- Multiplying both sides by $d + 1$,
    rw [show (d+1)*(polynomial.bernoulli d).eval (1 + x) = (d+1)*((polynomial.bernoulli d).eval x + d * x^(d - 1)),
    from eq.trans (mul_right_inj (d+1)).mp (mul_add ((polynomial.bernoulli d).eval x) (d * x^(d - 1)) (d+1)),
    show (d+1)*(polynomial.bernoulli d).eval (1 + x) = (d+1)*(polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)),
    from by ring,],
    -- 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
    rw [show (d+1)*(polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) =
    (d+1)*((polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (d+1)*(polynomial.bernoulli d).eval x),
    from by simp [add_mul],
    show (d+1)*((polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (d+1)*(polynomial.bernoulli d).eval x) =
    (d+1)*(polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (d+1)*(polynomial.bernoulli d).eval x,
    from by ring,],
    rw [show (d+1)*((polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (polynomial.bernoulli d).eval x) =
    (d+1)*((polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (polynomial.bernoulli d).eval x) +
    (d+1)*(polynomial.bernoulli d).eval x - (d+1)*(polynomial.bernoulli d).eval x,
    from by ring,],
    rw [show (d+1)*((polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (polynomial.bernoulli d).eval x) =
    (d+1)*((polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (polynomial.bernoulli d).eval x) +
    (d+1)*(polynomial.bernoulli d).eval x - (d+1)*(polynomial.bernoulli d).eval x,
    from by ring,],
    -- we get that
    -- $$ (d + 1) (1 + x)^d - (d + 1) x^d = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
    rw [show (d+1)*((polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (polynomial.bernoulli d).eval x) =
    (d+1)*(polynomial.bernoulli d).eval x - (d+1)*(polynomial.bernoulli d).eval x,
    from by ring,
    show (d+1)*(polynomial.bernoulli d).eval x - (d+1)*(polynomial.bernoulli d).eval x =
    (d+1)*(polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (d+1)*(polynomial.bernoulli d).eval x,
    from by ring,
    show (d+1)*(polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (d+1)*(polynomial.bernoulli d).eval x =
    (d+1)*(polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (d+1)*(polynomial.bernoulli d).eval x,
    from by ring,
    show (d+1)*(polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (d+1)*(polynomial.bernoulli d).eval x =
    (d+1)*((polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (polynomial.bernoulli d).eval x),
    from by ring,],
    rw [show (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)) - (polynomial.bernoulli d).eval x,
    from by ring,
    show (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)),
    from by ring,],
    rw [show (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + (d+1)*(d * x^(d - 1)),
    from by ring,
    show (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + d * x^(d - 1),
    from by ring,],
  },
  show (polynomial.bernoulli (d+1)).eval (1 + x) = (polynomial.bernoulli (d+1)).eval x + (d+1) * x^(d - 1 + 1),
  from by rw [show d + 1 = (d - 1 + 1) + 1, from by ring,
    show (polynomial.bernoulli (d - 1 + 1 + 1)).eval (1 + x) = (polynomial.bernoulli (d - 1 + 1 + 1)).eval x + (d - 1 + 1 + 1) * x^(d - 1 + 1 + 1 - 1),
    from by rw [show d = d - 1 + 1, from by ring,
      show (polynomial.bernoulli (d - 1 + 1 + 1)).eval (1 + x) = (polynomial.bernoulli (d - 1 + 1 + 1)).eval x + (d - 1 + 1 + 1) * x^(d - 1 + 1),
      from
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
  have h1 : ∀ m : ℕ, m < n → (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by {
    assume m : ℕ,
    assume hm : m < n,
    induction m with m hm,
    show (polynomial.bernoulli 0).eval (1 + x) = (polynomial.bernoulli 0).eval x + 0 * x^(-1 + 0), by {
      simp,
    },
    show (polynomial.bernoulli (m + 1)).eval (1 + x) = (polynomial.bernoulli (m + 1)).eval x + (m + 1) * x^(-1 + m + 1), from by {
      simp,
      have h1 : (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1), from by {
        apply hm,
        apply nat.lt_succ_of_lt hm,
      },
      rw h1,
      ring,
    },
  },
  have h2 : (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1), from by {
    apply h1,
    apply nat.lt_succ_self,
  },
  show (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1), from by {
    apply h2,
  },
end

/- 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 {split,apply set.subset_of_mem_powerset,exact hA,apply set.subset_of_mem_powerset,exact hB},
  -- Then $(A ∩ B) ⊆ A$, by intersection of set is a subset
  have h2 : (A ∩ B) ⊆ A, from by apply set.inter_subset_left,
  -- Then $(A ∩ B) ⊆ S$, by subset relation is transitive
  have h3 : (A ∩ B) ⊆ S, from by {apply set.subset.trans h2 h1.left},
  -- Hence $(A ∩ B) ∈  𝒫 S$, by power set definition
  show (A ∩ B) ∈  𝒫 S, from by {apply set.mem_powerset h3},
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 rw sq
  -- distributive property of multiplication over addition gives:
  ... = x*(x+y) + y*(x+y) : by rw add_mul
  -- applying the above property further gives:
  ... = x*x + x*y + y*x + y*y : by {rw [mul_comm x (x+y),mul_comm y (x+y)], rw [add_mul,add_mul], ring}
  -- rearranging the terms using commutativity and adding gives:
  ... = x^2 + 2*x*y + y^2 : by {repeat {rw ← sq}, rw mul_comm y x, 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 {
    assume a b : G, use a⁻¹ * b, obviously, },
  have h2 : ∀ a b : G, ∃! y : G, y * a = b, from by {
    assume a b : G, use b * a⁻¹, obviously, },

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

  -- 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 assume a :G,
    exists_unique.unique (h3 a) (classical.some_spec (exists_unique.exists (h3 a)))
    (mul_one a),
  have h6 : ∀ a : G, classical.some (h4 a).exists = (1 : G), from assume a : G,
    exists_unique.unique (h4 a) (classical.some_spec (exists_unique.exists (h4 a))) (one_mul a),

  show ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a, from by {
    use (1 : G),
    have h7 : ∀ e : G, (∀ a : G, e * a = a ∧ a * e = a) → e = 1, from by {
      assume (e : G) (hident : ∀ a : G, e * a = a ∧ a * e = a),
      have h8 : ∀ a : G, e = classical.some (h3 a).exists, from assume (a : G),
        exists_unique.unique (h3 a) (hident a).right
        (classical.some_spec (exists_unique.exists (h3 a))),
      have h9 : ∀ a : G, e = classical.some (h4 a).exists, from assume (a : G),
        exists_unique.unique (h4 a) (hident a).left
        (classical.some_spec (exists_unique.exists (h4 a))),
      show e = (1 : G), from eq.trans (h9 e) (h6 _),
    },
    exact ⟨by obviously, h7⟩,
  }
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) 2022 Aaron Anderson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Aaron Anderson

! This file was ported from Lean 3 source module model_theory.ultraproducts
! leanprover-community/mathlib commit f1ae620609496a37534c2ab3640b641d5be8b6f0
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.ModelTheory.Quotients
import Mathbin.Order.Filter.Germ
import Mathbin.Order.Filter.Ultrafilter

/-! # Ultraproducts and Łoś's Theorem

## Main Definitions
* `first_order.language.ultraproduct.Structure` is the ultraproduct structure on `filter.product`.

## Main Results
* Łoś's Theorem: `first_order.language.ultraproduct.sentence_realize`. An ultraproduct models a
sentence `φ` if and only if the set of structures in the product that model `φ` is in the
ultrafilter.

## Tags
ultraproduct, Los's theorem

-/

universe u v

variable {α : Type _} (M : α → Type _) (u : Ultrafilter α)

open FirstOrder Filter

open Filter

namespace FirstOrder

namespace Language

open Structure

variable {L : Language.{u, v}} [∀ a, L.Structure (M a)]

namespace Ultraproduct

instance setoidPrestructure : L.Prestructure ((u : Filter α).productSetoid M) :=
  {
    (u : Filter α).productSetoid
      M with
    toStructure :=
      { funMap := fun n f x a => funMap f fun i => x i a
        rel_map := fun n r x => ∀ᶠ a : α in u, RelMap r fun i => x i a }
    fun_equiv := fun n f x y xy =>
      by
      refine' mem_of_superset (Inter_mem.2 xy) fun a ha => _
      simp only [Set.mem_interᵢ, Set.mem_setOf_eq] at ha
      simp only [Set.mem_setOf_eq, ha]
    rel_equiv := fun n r x y xy => by
      rw [← iff_eq_eq]
      refine' ⟨fun hx => _, fun hy => _⟩
      · refine' mem_of_superset (inter_mem hx (Inter_mem.2 xy)) _
        rintro a ⟨ha1, ha2⟩
        simp only [Set.mem_interᵢ, Set.mem_setOf_eq] at *
        rw [← funext ha2]
        exact ha1
      · refine' mem_of_superset (inter_mem hy (Inter_mem.2 xy)) _
        rintro a ⟨ha1, ha2⟩
        simp only [Set.mem_interᵢ, Set.mem_setOf_eq] at *
        rw [funext ha2]
        exact ha1 }
#align first_order.language.ultraproduct.setoid_prestructure FirstOrder.Language.Ultraproduct.setoidPrestructure

variable {M} {u}

instance structure : L.Structure ((u : Filter α).product M) :=
  Language.quotientStructure
#align first_order.language.ultraproduct.Structure FirstOrder.Language.Ultraproduct.structure

theorem funMap_cast {n : ℕ} (f : L.Functions n) (x : Fin n → ∀ a, M a) :
    (funMap f fun i => (x i : (u : Filter α).product M)) = fun a => funMap f fun i => x i a := by
  apply fun_map_quotient_mk
#align first_order.language.ultraproduct.fun_map_cast FirstOrder.Language.Ultraproduct.funMap_cast

theorem term_realize_cast {β : Type _} (x : β → ∀ a, M a) (t : L.term β) :
    (t.realize fun i => (x i : (u : Filter α).product M)) = fun a => t.realize fun i => x i a :=
  by
  convert@term.realize_quotient_mk L _ ((u : Filter α).productSetoid M)
      (ultraproduct.setoid_prestructure M u) _ t x
  ext a
  induction t
  · rfl
  · simp only [term.realize, t_ih]
    rfl
#align first_order.language.ultraproduct.term_realize_cast FirstOrder.Language.Ultraproduct.term_realize_cast

variable [∀ a : α, Nonempty (M a)]

theorem boundedFormula_realize_cast {β : Type _} {n : ℕ} (φ : L.BoundedFormula β n)
    (x : β → ∀ a, M a) (v : Fin n → ∀ a, M a) :
    (φ.realize (fun i : β => (x i : (u : Filter α).product M)) fun i => v i) ↔
      ∀ᶠ a : α in u, φ.realize (fun i : β => x i a) fun i => v i a :=
  by
  letI := (u : Filter α).productSetoid M
  induction' φ with _ _ _ _ _ _ _ _ m _ _ ih ih' k φ ih
  · simp only [bounded_formula.realize, eventually_const]
  · have h2 : ∀ a : α, (Sum.elim (fun i : β => x i a) fun i => v i a) = fun i => Sum.elim x v i a :=
      fun a => funext fun i => Sum.casesOn i (fun i => rfl) fun i => rfl
    simp only [bounded_formula.realize, (Sum.comp_elim coe x v).symm, h2, term_realize_cast]
    exact Quotient.eq''
  · have h2 : ∀ a : α, (Sum.elim (fun i : β => x i a) fun i => v i a) = fun i => Sum.elim x v i a :=
      fun a => funext fun i => Sum.casesOn i (fun i => rfl) fun i => rfl
    simp only [bounded_formula.realize, (Sum.comp_elim coe x v).symm, term_realize_cast, h2]
    exact rel_map_quotient_mk _ _
  · simp only [bounded_formula.realize, ih v, ih' v]
    rw [Ultrafilter.eventually_imp]
  · simp only [bounded_formula.realize]
    trans
      ∀ m : ∀ a : α, M a,
        φ.realize (fun i : β => (x i : (u : Filter α).product M))
          (Fin.snoc (coe ∘ v) (↑m : (u : Filter α).product M))
    · exact forall_quotient_iff
    have h' :
      ∀ (m : ∀ a, M a) (a : α),
        (fun i : Fin (k + 1) => (Fin.snoc v m : _ → ∀ a, M a) i a) =
          Fin.snoc (fun i : Fin k => v i a) (m a) :=
      by
      refine' fun m a => funext (Fin.reverseInduction _ fun i hi => _)
      · simp only [Fin.snoc_last]
      · simp only [Fin.snoc_cast_succ]
    simp only [← Fin.comp_snoc, ih, h']
    refine' ⟨fun h => _, fun h m => _⟩
    · contrapose! h
      simp_rw [← Ultrafilter.eventually_not, not_forall] at h
      refine'
        ⟨fun a : α =>
          Classical.epsilon fun m : M a =>
            ¬φ.realize (fun i => x i a) (Fin.snoc (fun i => v i a) m),
          _⟩
      rw [← Ultrafilter.eventually_not]
      exact Filter.mem_of_superset h fun a ha => Classical.epsilon_spec ha
    · rw [Filter.eventually_iff] at *
      exact Filter.mem_of_superset h fun a ha => ha (m a)
#align first_order.language.ultraproduct.bounded_formula_realize_cast FirstOrder.Language.Ultraproduct.boundedFormula_realize_cast

theorem realize_formula_cast {β : Type _} (φ : L.Formula β) (x : β → ∀ a, M a) :
    (φ.realize fun i => (x i : (u : Filter α).product M)) ↔
      ∀ᶠ a : α in u, φ.realize fun i => x i a :=
  by
  simp_rw [formula.realize, ← bounded_formula_realize_cast φ x, iff_eq_eq]
  exact congr rfl (Subsingleton.elim _ _)
#align first_order.language.ultraproduct.realize_formula_cast FirstOrder.Language.Ultraproduct.realize_formula_cast

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- Łoś's Theorem : A sentence is true in an ultraproduct if and only if the set of structures it is
  true in is in the ultrafilter. -/
theorem sentence_realize (φ : L.Sentence) : (u : Filter α).product M ⊨ φ ↔ ∀ᶠ a : α in u, M a ⊨ φ :=
  by
  simp_rw [sentence.realize, ← realize_formula_cast φ, iff_eq_eq]
  exact congr rfl (Subsingleton.elim _ _)
#align first_order.language.ultraproduct.sentence_realize FirstOrder.Language.Ultraproduct.sentence_realize

instance : Nonempty ((u : Filter α).product M) :=
  letI : ∀ a, Inhabited (M a) := fun _ => Classical.inhabited_of_nonempty'
  instNonempty

end Ultraproduct

end Language

end FirstOrder