/-
Copyright (c) 2022 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import topology.metric_space.metrizable

/-!
# Metrizable uniform spaces

In this file we prove that a uniform space with countably generated uniformity filter is
pseudometrizable: there exists a `pseudo_metric_space` structure that generates the same uniformity.
The proof follows [Sergey Melikhov, Metrizable uniform spaces][melikhov2011].
## Main definitions

* `pseudo_metric_space.of_prenndist`: given a function `d : X → X → ℝ≥0` such that `d x x = 0` and
  `d x y = d y x` for all `x y : X`, constructs the maximal pseudo metric space structure such that
  `nndist x y ≤ d x y` for all `x y : X`.

* `uniform_space.pseudo_metric_space`: given a uniform space `X` with countably generated `𝓤 X`,
  constructs a `pseudo_metric_space X` instance that is compatible with the uniform space structure.

* `uniform_space.metric_space`: given a T₀ uniform space `X` with countably generated `𝓤 X`,
  constructs a `metric_space X` instance that is compatible with the uniform space structure.

## Main statements

* `uniform_space.metrizable_uniformity`: if `X` is a uniform space with countably generated `𝓤 X`,
  then there exists a `pseudo_metric_space` structure that is compatible with this `uniform_space`
  structure. Use `uniform_space.pseudo_metric_space` or `uniform_space.metric_space` instead.

* `uniform_space.pseudo_metrizable_space`: a uniform space with countably generated `𝓤 X` is pseudo
  metrizable.

* `uniform_space.metrizable_space`: a T₀ uniform space with countably generated `𝓤 X` is
  metrizable. This is not an instance to avoid loops.

## Tags

metrizable space, uniform space
-/

open set function metric list filter
open_locale nnreal filter uniformity

variables {X : Type*}

namespace pseudo_metric_space

/-- The maximal pseudo metric space structure on `X` such that `dist x y ≤ d x y` for all `x y`,
where `d : X → X → ℝ≥0` is a function such that `d x x = 0` and `d x y = d y x` for all `x`, `y`. -/
noncomputable def of_prenndist (d : X → X → ℝ≥0) (dist_self : ∀ x, d x x = 0)
  (dist_comm : ∀ x y, d x y = d y x) :
  pseudo_metric_space X :=
{ dist := λ x y, ↑(⨅ l : list X, ((x :: l).zip_with d (l ++ [y])).sum : ℝ≥0),
  dist_self := λ x, (nnreal.coe_eq_zero _).2 $ nonpos_iff_eq_zero.1 $
    (cinfi_le (order_bot.bdd_below _) []).trans_eq $ by simp [dist_self],
  dist_comm := λ x y, nnreal.coe_eq.2 $
    begin
      refine reverse_surjective.infi_congr _ (λ l, _),
      rw [← sum_reverse, zip_with_distrib_reverse, reverse_append, reverse_reverse,
        reverse_singleton, singleton_append, reverse_cons, reverse_reverse,
        zip_with_comm_of_comm _ dist_comm],
      simp only [length, length_append]
    end,
  dist_triangle := λ x y z,
    begin
      rw [← nnreal.coe_add, nnreal.coe_le_coe],
      refine nnreal.le_infi_add_infi (λ lxy lyz, _),
      calc (⨅ l, (zip_with d (x :: l) (l ++ [z])).sum) ≤
        (zip_with d (x :: (lxy ++ y :: lyz)) ((lxy ++ y :: lyz) ++ [z])).sum :
        cinfi_le (order_bot.bdd_below _) (lxy ++ y :: lyz)
      ... = (zip_with d (x :: lxy) (lxy ++ [y])).sum + (zip_with d (y :: lyz) (lyz ++ [z])).sum : _,
      rw [← sum_append, ← zip_with_append, cons_append, ← @singleton_append _ y, append_assoc,
        append_assoc, append_assoc],
      rw [length_cons, length_append, length_singleton]
    end }

lemma dist_of_prenndist (d : X → X → ℝ≥0) (dist_self : ∀ x, d x x = 0)
  (dist_comm : ∀ x y, d x y = d y x) (x y : X) :
  @dist X (@pseudo_metric_space.to_has_dist X
    (pseudo_metric_space.of_prenndist d dist_self dist_comm)) x y =
    ↑(⨅ l : list X, ((x :: l).zip_with d (l ++ [y])).sum : ℝ≥0) := rfl

lemma dist_of_prenndist_le (d : X → X → ℝ≥0) (dist_self : ∀ x, d x x = 0)
  (dist_comm : ∀ x y, d x y = d y x) (x y : X) :
  @dist X (@pseudo_metric_space.to_has_dist X
    (pseudo_metric_space.of_prenndist d dist_self dist_comm)) x y ≤ d x y :=
nnreal.coe_le_coe.2 $ (cinfi_le (order_bot.bdd_below _) []).trans_eq $ by simp

/-- Consider a function `d : X → X → ℝ≥0` such that `d x x = 0` and `d x y = d y x` for all `x`,
`y`. Let `dist` be the largest pseudometric distance such that `dist x y ≤ d x y`, see
`pseudo_metric_space.of_prenndist`. Suppose that `d` satisfies the following triangle-like
inequality: `d x₁ x₄ ≤ 2 * max (d x₁ x₂, d x₂ x₃, d x₃ x₄)`. Then `d x y ≤ 2 * dist x y` for all
`x`, `y`. -/
lemma le_two_mul_dist_of_prenndist (d : X → X → ℝ≥0) (dist_self : ∀ x, d x x = 0)
  (dist_comm : ∀ x y, d x y = d y x)
  (hd : ∀ x₁ x₂ x₃ x₄, d x₁ x₄ ≤ 2 * max (d x₁ x₂) (max (d x₂ x₃) (d x₃ x₄))) (x y : X) :
  ↑(d x y) ≤ 2 * @dist X (@pseudo_metric_space.to_has_dist X
    (pseudo_metric_space.of_prenndist d dist_self dist_comm)) x y :=
begin
  /- We need to show that `d x y` is at most twice the sum `L` of `d xᵢ xᵢ₊₁` over a path
  `x₀=x, ..., xₙ=y`. We prove it by induction on the length `n` of the sequence. Find an edge that
  splits the path into two parts of almost equal length: both `d x₀ x₁ + ... + d xₖ₋₁ xₖ` and
  `d xₖ₊₁ xₖ₊₂ + ... + d xₙ₋₁ xₙ` are less than or equal to `L / 2`.
  Then `d x₀ xₖ ≤ L`, `d xₖ xₖ₊₁ ≤ L`, and `d xₖ₊₁ xₙ ≤ L`, thus `d x₀ xₙ ≤ 2 * L`. -/
  rw [dist_of_prenndist, ← nnreal.coe_two, ← nnreal.coe_mul, nnreal.mul_infi, nnreal.coe_le_coe],
  refine le_cinfi (λ l, _),
  have hd₀_trans : transitive (λ x y, d x y = 0),
  { intros a b c hab hbc,
    rw ← nonpos_iff_eq_zero,
    simpa only [*, max_eq_right, mul_zero] using hd a b c c },
  haveI : is_trans X (λ x y, d x y = 0) := ⟨hd₀_trans⟩,
  induction hn : length l using nat.strong_induction_on with n ihn generalizing x y l,
  simp only at ihn, subst n,
  set L := zip_with d (x :: l) (l ++ [y]),
  have hL_len : length L = length l + 1, by simp,
  cases eq_or_ne (d x y) 0 with hd₀ hd₀, { simp only [hd₀, zero_le] },
  rsuffices ⟨z, z', hxz, hzz', hz'y⟩ : ∃ z z' : X, d x z ≤ L.sum ∧ d z z' ≤ L.sum ∧ d z' y ≤ L.sum,
  { exact (hd x z z' y).trans (mul_le_mul_left' (max_le hxz (max_le hzz' hz'y)) _) },
  set s : set ℕ := {m : ℕ | 2 * (take m L).sum ≤ L.sum},
  have hs₀ : 0 ∈ s, by simp [s],
  have hsne : s.nonempty, from ⟨0, hs₀⟩,
  obtain ⟨M, hMl, hMs⟩ : ∃ M ≤ length l, is_greatest s M,
  { have hs_ub : length l ∈ upper_bounds s,
    { intros m hm,
      rw [← not_lt, nat.lt_iff_add_one_le, ← hL_len],
      intro hLm,
      rw [mem_set_of_eq, take_all_of_le hLm, two_mul, add_le_iff_nonpos_left, nonpos_iff_eq_zero,
        sum_eq_zero_iff, ← all₂_iff_forall, all₂_zip_with, ← chain_append_singleton_iff_forall₂]
        at hm; [skip, by simp],
      exact hd₀ (hm.rel (mem_append.2 $ or.inr $ mem_singleton_self _)) },
    have hs_bdd : bdd_above s, from ⟨length l, hs_ub⟩,
    exact ⟨Sup s, cSup_le hsne hs_ub, ⟨nat.Sup_mem hsne hs_bdd, λ k, le_cSup hs_bdd⟩⟩ },
  have hM_lt : M < length L, by rwa [hL_len, nat.lt_succ_iff],
  have hM_ltx : M < length (x :: l), from lt_length_left_of_zip_with hM_lt,
  have hM_lty : M < length (l ++ [y]), from lt_length_right_of_zip_with hM_lt,
  refine ⟨(x :: l).nth_le M hM_ltx, (l ++ [y]).nth_le M hM_lty, _, _, _⟩,
  { cases M, { simp [dist_self] },
    rw nat.succ_le_iff at hMl,
    have hMl' : length (take M l) = M, from (length_take _ _).trans (min_eq_left hMl.le),
    simp only [nth_le],
    refine (ihn _ hMl _ _ _ hMl').trans _,
    convert hMs.1.out,
    rw [zip_with_distrib_take, take, take_succ, nth_append hMl, nth_le_nth hMl,
      ← option.coe_def, option.to_list_some, take_append_of_le_length hMl.le],
    refl },
  { refine single_le_sum (λ x hx, zero_le x) _ (mem_iff_nth_le.2 ⟨M, hM_lt, _⟩),
    apply nth_le_zip_with },
  { rcases hMl.eq_or_lt with rfl|hMl,
    { simp only [nth_le_append_right le_rfl, sub_self, nth_le_singleton, dist_self, zero_le] },
    rw [nth_le_append _ hMl],
    have hlen : length (drop (M + 1) l) = length l - (M + 1), from length_drop _ _,
    have hlen_lt : length l - (M + 1) < length l, from nat.sub_lt_of_pos_le _ _ M.succ_pos hMl,
    refine (ihn _ hlen_lt _ y _ hlen).trans _,
    rw [cons_nth_le_drop_succ],
    have hMs' : L.sum ≤ 2 * (L.take (M + 1)).sum,
      from not_lt.1 (λ h, (hMs.2 h.le).not_lt M.lt_succ_self),
    rw [← sum_take_add_sum_drop L (M + 1), two_mul, add_le_add_iff_left,
      ← add_le_add_iff_right, sum_take_add_sum_drop, ← two_mul] at hMs',
    convert hMs',
    rwa [zip_with_distrib_drop, drop, drop_append_of_le_length] }
end

end pseudo_metric_space

/-- If `X` is a uniform space with countably generated uniformity filter, there exists a
`pseudo_metric_space` structure compatible with the `uniform_space` structure. Use
`uniform_space.pseudo_metric_space` or `uniform_space.metric_space` instead. -/
protected lemma uniform_space.metrizable_uniformity (X : Type*) [uniform_space X]
  [is_countably_generated (𝓤 X)] :
  ∃ I : pseudo_metric_space X, I.to_uniform_space = ‹_› :=
begin
  /- Choose a fast decreasing antitone basis `U : ℕ → set (X × X)` of the uniformity filter `𝓤 X`.
  Define `d x y : ℝ≥0` to be `(1 / 2) ^ n`, where `n` is the minimal index of `U n` that separates
  `x` and `y`: `(x, y) ∉ U n`, or `0` if `x` is not separated from `y`. This function satisfies the
  assumptions of `pseudo_metric_space.of_prenndist` and
  `pseudo_metric_space.le_two_mul_dist_of_prenndist`, hence the distance given by the former pseudo
  metric space structure is Lipschitz equivalent to the `d`. Thus the uniformities generated by
  `d` and `dist` are equal. Since the former uniformity is equal to `𝓤 X`, the latter is equal to
  `𝓤 X` as well. -/
  classical,
  obtain ⟨U, hU_symm, hU_comp, hB⟩ : ∃ U : ℕ → set (X × X), (∀ n, symmetric_rel (U n)) ∧
    (∀ ⦃m n⦄, m < n → U n ○ (U n ○ U n) ⊆ U m) ∧ (𝓤 X).has_antitone_basis U,
  { rcases uniform_space.has_seq_basis X with ⟨V, hB, hV_symm⟩,
    rcases hB.subbasis_with_rel (λ m, hB.tendsto_small_sets.eventually
      (eventually_uniformity_iterate_comp_subset (hB.mem m) 2)) with ⟨φ, hφ_mono, hφ_comp, hφB⟩,
    exact ⟨V ∘ φ, λ n, hV_symm _, hφ_comp, hφB⟩ },
  letI := uniform_space.separation_setoid X,
  set d : X → X → ℝ≥0 := λ x y, if h : ∃ n, (x, y) ∉ U n then (1 / 2) ^ nat.find h else 0,
  have hd₀ : ∀ {x y}, d x y = 0 ↔ x ≈ y,
  { intros x y, dsimp only [d],
    refine iff.trans _ hB.to_has_basis.mem_separation_rel.symm,
    simp only [true_implies_iff],
    split_ifs with h,
    { rw [← not_forall] at h, simp [h, pow_eq_zero_iff'] },
    { simpa only [not_exists, not_not, eq_self_iff_true, true_iff] using h } },
  have hd_symm : ∀ x y, d x y = d y x,
  { intros x y, dsimp only [d],
    simp only [@symmetric_rel.mk_mem_comm _ _ (hU_symm _) x y] },
  have hr : (1 / 2 : ℝ≥0) ∈ Ioo (0 : ℝ≥0) 1,
    from ⟨half_pos one_pos, nnreal.half_lt_self one_ne_zero⟩,
  letI I := pseudo_metric_space.of_prenndist d (λ x, hd₀.2 (setoid.refl _)) hd_symm,
  have hdist_le : ∀ x y, dist x y ≤ d x y,
    from pseudo_metric_space.dist_of_prenndist_le _ _ _,
  have hle_d : ∀ {x y : X} {n : ℕ}, (1 / 2) ^ n ≤ d x y ↔ (x, y) ∉ U n,
  { intros x y n,
    simp only [d], split_ifs with h,
    { rw [(strict_anti_pow hr.1 hr.2).le_iff_le, nat.find_le_iff],
      exact ⟨λ ⟨m, hmn, hm⟩ hn, hm (hB.antitone hmn hn), λ h, ⟨n, le_rfl, h⟩⟩ },
    { push_neg at h,
      simp only [h, not_true, (pow_pos hr.1 _).not_le] } },
  have hd_le : ∀ x y, ↑(d x y) ≤ 2 * dist x y,
  { refine pseudo_metric_space.le_two_mul_dist_of_prenndist _ _ _ (λ x₁ x₂ x₃ x₄, _),
    by_cases H : ∃ n, (x₁, x₄) ∉ U n,
    { refine (dif_pos H).trans_le _,
      rw [← nnreal.div_le_iff' two_ne_zero, ← mul_one_div (_ ^ _), ← pow_succ'],
      simp only [le_max_iff, hle_d, ← not_and_distrib],
      rintro ⟨h₁₂, h₂₃, h₃₄⟩,
      refine nat.find_spec H (hU_comp (lt_add_one $ nat.find H) _),
      exact ⟨x₂, h₁₂, x₃, h₂₃, h₃₄⟩ },
    { exact (dif_neg H).trans_le (zero_le _) } },
  refine ⟨I, uniform_space_eq $ (uniformity_basis_dist_pow hr.1 hr.2).ext hB.to_has_basis _ _⟩,
  { refine λ n hn, ⟨n, hn, λ x hx, (hdist_le _ _).trans_lt _⟩,
    rwa [← nnreal.coe_pow, nnreal.coe_lt_coe, ← not_le, hle_d, not_not, prod.mk.eta] },
  { refine λ n hn, ⟨n + 1, trivial, λ x hx, _⟩,
    rw [mem_set_of_eq] at hx,
    contrapose! hx,
    refine le_trans _ ((div_le_iff' (zero_lt_two' ℝ)).2 (hd_le x.1 x.2)),
    rwa [← nnreal.coe_two, ← nnreal.coe_div, ← nnreal.coe_pow, nnreal.coe_le_coe, pow_succ',
      mul_one_div, nnreal.div_le_iff two_ne_zero, div_mul_cancel _ (two_ne_zero' ℝ≥0),
      hle_d, prod.mk.eta] }
end

/-- A `pseudo_metric_space` instance compatible with a given `uniform_space` structure. -/
protected noncomputable def uniform_space.pseudo_metric_space (X : Type*) [uniform_space X]
  [is_countably_generated (𝓤 X)] : pseudo_metric_space X :=
(uniform_space.metrizable_uniformity X).some.replace_uniformity $
  congr_arg _ (uniform_space.metrizable_uniformity X).some_spec.symm

/-- A `metric_space` instance compatible with a given `uniform_space` structure. -/
protected noncomputable def uniform_space.metric_space (X : Type*) [uniform_space X]
  [is_countably_generated (𝓤 X)] [t0_space X] : metric_space X :=
@metric_space.of_t0_pseudo_metric_space X (uniform_space.pseudo_metric_space X) _

/-- A uniform space with countably generated `𝓤 X` is pseudo metrizable. -/
@[priority 100]
instance uniform_space.pseudo_metrizable_space [uniform_space X] [is_countably_generated (𝓤 X)] :
  topological_space.pseudo_metrizable_space X :=
by { letI := uniform_space.pseudo_metric_space X, apply_instance }

/-- A T₀ uniform space with countably generated `𝓤 X` is metrizable. This is not an instance to
avoid loops. -/
lemma uniform_space.metrizable_space [uniform_space X] [is_countably_generated (𝓤 X)] [t0_space X] :
  topological_space.metrizable_space X :=
by { letI := uniform_space.metric_space X, apply_instance }


import data.real.basic

def fn_ub (f : ℝ → ℝ) (a : ℝ) : Prop := ∀ x, f x ≤ a
def fn_has_ub (f : ℝ → ℝ) := ∃ a, fn_ub f a

open_locale classical

variable (f : ℝ → ℝ)

-- BEGIN
example (h : ¬ fn_has_ub f) : ∀ a, ∃ x, f x > a :=
begin
  contrapose! h,
  exact h,
end

example (x : ℝ) (h : ∀ ε > 0, x ≤ ε) : x ≤ 0 :=
begin
  contrapose! h,
  use x / 2,
  split,
  linarith,
  linarith,
end

example (x : ℝ) (h : ∀ ε > 0, x ≤ ε) : x ≤ 0 :=
begin
  contrapose! h,
  use x / 2,
  split; linarith,
end

-- END


/-
Copyright (c) 2022 Kexing Ying. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kexing Ying, Rémy Degenne

! This file was ported from Lean 3 source module probability.process.hitting_time
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Probability.Process.Stopping

/-!
# Hitting time

Given a stochastic process, the hitting time provides the first time the process ``hits'' some
subset of the state space. The hitting time is a stopping time in the case that the time index is
discrete and the process is adapted (this is true in a far more general setting however we have
only proved it for the discrete case so far).

## Main definition

* `measure_theory.hitting`: the hitting time of a stochastic process

## Main results

* `measure_theory.hitting_is_stopping_time`: a discrete hitting time of an adapted process is a
  stopping time

## Implementation notes

In the definition of the hitting time, we bound the hitting time by an upper and lower bound.
This is to ensure that our result is meaningful in the case we are taking the infimum of an
empty set or the infimum of a set which is unbounded from below. With this, we can talk about
hitting times indexed by the natural numbers or the reals. By taking the bounds to be
`⊤` and `⊥`, we obtain the standard definition in the case that the index is `ℕ∞` or `ℝ≥0∞`.

-/

open Filter Order TopologicalSpace

open Classical MeasureTheory NNReal ENNReal Topology BigOperators

namespace MeasureTheory

variable {Ω β ι : Type _} {m : MeasurableSpace Ω}

/-- Hitting time: given a stochastic process `u` and a set `s`, `hitting u s n m` is the first time
`u` is in `s` after time `n` and before time `m` (if `u` does not hit `s` after time `n` and
before `m` then the hitting time is simply `m`).

The hitting time is a stopping time if the process is adapted and discrete. -/
noncomputable def hitting [Preorder ι] [InfSet ι] (u : ι → Ω → β) (s : Set β) (n m : ι) : Ω → ι :=
  fun x => if ∃ j ∈ Set.Icc n m, u j x ∈ s then infₛ (Set.Icc n m ∩ { i : ι | u i x ∈ s }) else m
#align measure_theory.hitting MeasureTheory.hitting

section Inequalities

variable [ConditionallyCompleteLinearOrder ι] {u : ι → Ω → β} {s : Set β} {n i : ι} {ω : Ω}

/-- This lemma is strictly weaker than `hitting_of_le`. -/
theorem hitting_of_lt {m : ι} (h : m < n) : hitting u s n m ω = m :=
  by
  simp_rw [hitting]
  have h_not : ¬∃ (j : ι)(H : j ∈ Set.Icc n m), u j ω ∈ s :=
    by
    push_neg
    intro j
    rw [Set.Icc_eq_empty_of_lt h]
    simp only [Set.mem_empty_iff_false, IsEmpty.forall_iff]
  simp only [h_not, if_false]
#align measure_theory.hitting_of_lt MeasureTheory.hitting_of_lt

theorem hitting_le {m : ι} (ω : Ω) : hitting u s n m ω ≤ m :=
  by
  cases' le_or_lt n m with h_le h_lt
  · simp only [hitting]
    split_ifs
    · obtain ⟨j, hj₁, hj₂⟩ := h
      exact (cinfₛ_le (BddBelow.inter_of_left bddBelow_Icc) (Set.mem_inter hj₁ hj₂)).trans hj₁.2
    · exact le_rfl
  · rw [hitting_of_lt h_lt]
#align measure_theory.hitting_le MeasureTheory.hitting_le

theorem not_mem_of_lt_hitting {m k : ι} (hk₁ : k < hitting u s n m ω) (hk₂ : n ≤ k) : u k ω ∉ s :=
  by
  classical
    intro h
    have hexists : ∃ j ∈ Set.Icc n m, u j ω ∈ s
    refine' ⟨k, ⟨hk₂, le_trans hk₁.le <| hitting_le _⟩, h⟩
    refine' not_le.2 hk₁ _
    simp_rw [hitting, if_pos hexists]
    exact cinfₛ_le bdd_below_Icc.inter_of_left ⟨⟨hk₂, le_trans hk₁.le <| hitting_le _⟩, h⟩
#align measure_theory.not_mem_of_lt_hitting MeasureTheory.not_mem_of_lt_hitting

theorem hitting_eq_end_iff {m : ι} :
    hitting u s n m ω = m ↔
      (∃ j ∈ Set.Icc n m, u j ω ∈ s) → infₛ (Set.Icc n m ∩ { i : ι | u i ω ∈ s }) = m :=
  by rw [hitting, ite_eq_right_iff]
#align measure_theory.hitting_eq_end_iff MeasureTheory.hitting_eq_end_iff

theorem hitting_of_le {m : ι} (hmn : m ≤ n) : hitting u s n m ω = m :=
  by
  obtain rfl | h := le_iff_eq_or_lt.1 hmn
  · simp only [hitting, Set.Icc_self, ite_eq_right_iff, Set.mem_Icc, exists_prop,
      forall_exists_index, and_imp]
    intro i hi₁ hi₂ hi
    rw [Set.inter_eq_left_iff_subset.2, cinfₛ_singleton]
    exact Set.singleton_subset_iff.2 (le_antisymm hi₂ hi₁ ▸ hi)
  · exact hitting_of_lt h
#align measure_theory.hitting_of_le MeasureTheory.hitting_of_le

theorem le_hitting {m : ι} (hnm : n ≤ m) (ω : Ω) : n ≤ hitting u s n m ω :=
  by
  simp only [hitting]
  split_ifs
  · refine' le_cinfₛ _ fun b hb => _
    · obtain ⟨k, hk_Icc, hk_s⟩ := h
      exact ⟨k, hk_Icc, hk_s⟩
    · rw [Set.mem_inter_iff] at hb
      exact hb.1.1
  · exact hnm
#align measure_theory.le_hitting MeasureTheory.le_hitting

theorem le_hitting_of_exists {m : ι} (h_exists : ∃ j ∈ Set.Icc n m, u j ω ∈ s) :
    n ≤ hitting u s n m ω := by
  refine' le_hitting _ ω
  by_contra
  rw [Set.Icc_eq_empty_of_lt (not_le.mp h)] at h_exists
  simpa using h_exists
#align measure_theory.le_hitting_of_exists MeasureTheory.le_hitting_of_exists

theorem hitting_mem_Icc {m : ι} (hnm : n ≤ m) (ω : Ω) : hitting u s n m ω ∈ Set.Icc n m :=
  ⟨le_hitting hnm ω, hitting_le ω⟩
#align measure_theory.hitting_mem_Icc MeasureTheory.hitting_mem_Icc

theorem hitting_mem_set [IsWellOrder ι (· < ·)] {m : ι} (h_exists : ∃ j ∈ Set.Icc n m, u j ω ∈ s) :
    u (hitting u s n m ω) ω ∈ s :=
  by
  simp_rw [hitting, if_pos h_exists]
  have h_nonempty : (Set.Icc n m ∩ { i : ι | u i ω ∈ s }).Nonempty :=
    by
    obtain ⟨k, hk₁, hk₂⟩ := h_exists
    exact ⟨k, Set.mem_inter hk₁ hk₂⟩
  have h_mem := cinfₛ_mem h_nonempty
  rw [Set.mem_inter_iff] at h_mem
  exact h_mem.2
#align measure_theory.hitting_mem_set MeasureTheory.hitting_mem_set

theorem hitting_mem_set_of_hitting_lt [IsWellOrder ι (· < ·)] {m : ι} (hl : hitting u s n m ω < m) :
    u (hitting u s n m ω) ω ∈ s :=
  by
  by_cases h : ∃ j ∈ Set.Icc n m, u j ω ∈ s
  · exact hitting_mem_set h
  · simp_rw [hitting, if_neg h] at hl
    exact False.elim (hl.ne rfl)
#align measure_theory.hitting_mem_set_of_hitting_lt MeasureTheory.hitting_mem_set_of_hitting_lt

theorem hitting_le_of_mem {m : ι} (hin : n ≤ i) (him : i ≤ m) (his : u i ω ∈ s) :
    hitting u s n m ω ≤ i :=
  by
  have h_exists : ∃ k ∈ Set.Icc n m, u k ω ∈ s := ⟨i, ⟨hin, him⟩, his⟩
  simp_rw [hitting, if_pos h_exists]
  exact cinfₛ_le (BddBelow.inter_of_left bddBelow_Icc) (Set.mem_inter ⟨hin, him⟩ his)
#align measure_theory.hitting_le_of_mem MeasureTheory.hitting_le_of_mem

theorem hitting_le_iff_of_exists [IsWellOrder ι (· < ·)] {m : ι}
    (h_exists : ∃ j ∈ Set.Icc n m, u j ω ∈ s) :
    hitting u s n m ω ≤ i ↔ ∃ j ∈ Set.Icc n i, u j ω ∈ s :=
  by
  constructor <;> intro h'
  · exact ⟨hitting u s n m ω, ⟨le_hitting_of_exists h_exists, h'⟩, hitting_mem_set h_exists⟩
  · have h'' : ∃ k ∈ Set.Icc n (min m i), u k ω ∈ s :=
      by
      obtain ⟨k₁, hk₁_mem, hk₁_s⟩ := h_exists
      obtain ⟨k₂, hk₂_mem, hk₂_s⟩ := h'
      refine' ⟨min k₁ k₂, ⟨le_min hk₁_mem.1 hk₂_mem.1, min_le_min hk₁_mem.2 hk₂_mem.2⟩, _⟩
      exact min_rec' (fun j => u j ω ∈ s) hk₁_s hk₂_s
    obtain ⟨k, hk₁, hk₂⟩ := h''
    refine' le_trans _ (hk₁.2.trans (min_le_right _ _))
    exact hitting_le_of_mem hk₁.1 (hk₁.2.trans (min_le_left _ _)) hk₂
#align measure_theory.hitting_le_iff_of_exists MeasureTheory.hitting_le_iff_of_exists

theorem hitting_le_iff_of_lt [IsWellOrder ι (· < ·)] {m : ι} (i : ι) (hi : i < m) :
    hitting u s n m ω ≤ i ↔ ∃ j ∈ Set.Icc n i, u j ω ∈ s :=
  by
  by_cases h_exists : ∃ j ∈ Set.Icc n m, u j ω ∈ s
  · rw [hitting_le_iff_of_exists h_exists]
  · simp_rw [hitting, if_neg h_exists]
    push_neg  at h_exists
    simp only [not_le.mpr hi, Set.mem_Icc, false_iff_iff, not_exists, and_imp]
    exact fun k hkn hki => h_exists k ⟨hkn, hki.trans hi.le⟩
#align measure_theory.hitting_le_iff_of_lt MeasureTheory.hitting_le_iff_of_lt

theorem hitting_lt_iff [IsWellOrder ι (· < ·)] {m : ι} (i : ι) (hi : i ≤ m) :
    hitting u s n m ω < i ↔ ∃ j ∈ Set.Ico n i, u j ω ∈ s :=
  by
  constructor <;> intro h'
  · have h : ∃ j ∈ Set.Icc n m, u j ω ∈ s := by
      by_contra
      simp_rw [hitting, if_neg h, ← not_le] at h'
      exact h' hi
    exact ⟨hitting u s n m ω, ⟨le_hitting_of_exists h, h'⟩, hitting_mem_set h⟩
  · obtain ⟨k, hk₁, hk₂⟩ := h'
    refine' lt_of_le_of_lt _ hk₁.2
    exact hitting_le_of_mem hk₁.1 (hk₁.2.le.trans hi) hk₂
#align measure_theory.hitting_lt_iff MeasureTheory.hitting_lt_iff

theorem hitting_eq_hitting_of_exists {m₁ m₂ : ι} (h : m₁ ≤ m₂)
    (h' : ∃ j ∈ Set.Icc n m₁, u j ω ∈ s) : hitting u s n m₁ ω = hitting u s n m₂ ω :=
  by
  simp only [hitting, if_pos h']
  obtain ⟨j, hj₁, hj₂⟩ := h'
  rw [if_pos]
  · refine'
      le_antisymm _
        (cinfₛ_le_cinfₛ bdd_below_Icc.inter_of_left ⟨j, hj₁, hj₂⟩
          (Set.inter_subset_inter_left _ (Set.Icc_subset_Icc_right h)))
    refine' le_cinfₛ ⟨j, Set.Icc_subset_Icc_right h hj₁, hj₂⟩ fun i hi => _
    by_cases hi' : i ≤ m₁
    · exact cinfₛ_le bdd_below_Icc.inter_of_left ⟨⟨hi.1.1, hi'⟩, hi.2⟩
    ·
      exact
        ((cinfₛ_le bdd_below_Icc.inter_of_left ⟨hj₁, hj₂⟩).trans (hj₁.2.trans le_rfl)).trans
          (le_of_lt (not_le.1 hi'))
  exact ⟨j, ⟨hj₁.1, hj₁.2.trans h⟩, hj₂⟩
#align measure_theory.hitting_eq_hitting_of_exists MeasureTheory.hitting_eq_hitting_of_exists

theorem hitting_mono {m₁ m₂ : ι} (hm : m₁ ≤ m₂) : hitting u s n m₁ ω ≤ hitting u s n m₂ ω :=
  by
  by_cases h : ∃ j ∈ Set.Icc n m₁, u j ω ∈ s
  · exact (hitting_eq_hitting_of_exists hm h).le
  · simp_rw [hitting, if_neg h]
    split_ifs with h'
    · obtain ⟨j, hj₁, hj₂⟩ := h'
      refine' le_cinfₛ ⟨j, hj₁, hj₂⟩ _
      by_contra hneg
      push_neg  at hneg
      obtain ⟨i, hi₁, hi₂⟩ := hneg
      exact h ⟨i, ⟨hi₁.1.1, hi₂.le⟩, hi₁.2⟩
    · exact hm
#align measure_theory.hitting_mono MeasureTheory.hitting_mono

end Inequalities

/-- A discrete hitting time is a stopping time. -/
theorem hitting_isStoppingTime [ConditionallyCompleteLinearOrder ι] [IsWellOrder ι (· < ·)]
    [Countable ι] [TopologicalSpace β] [PseudoMetrizableSpace β] [MeasurableSpace β] [BorelSpace β]
    {f : Filtration ι m} {u : ι → Ω → β} {s : Set β} {n n' : ι} (hu : Adapted f u)
    (hs : MeasurableSet s) : IsStoppingTime f (hitting u s n n') :=
  by
  intro i
  cases' le_or_lt n' i with hi hi
  · have h_le : ∀ ω, hitting u s n n' ω ≤ i := fun x => (hitting_le x).trans hi
    simp [h_le]
  · have h_set_eq_Union : { ω | hitting u s n n' ω ≤ i } = ⋃ j ∈ Set.Icc n i, u j ⁻¹' s :=
      by
      ext x
      rw [Set.mem_setOf_eq, hitting_le_iff_of_lt _ hi]
      simp only [Set.mem_Icc, exists_prop, Set.mem_unionᵢ, Set.mem_preimage]
    rw [h_set_eq_Union]
    exact
      MeasurableSet.unionᵢ fun j =>
        MeasurableSet.unionᵢ fun hj => f.mono hj.2 _ ((hu j).Measurable hs)
#align measure_theory.hitting_is_stopping_time MeasureTheory.hitting_isStoppingTime

theorem stoppedValue_hitting_mem [ConditionallyCompleteLinearOrder ι] [IsWellOrder ι (· < ·)]
    {u : ι → Ω → β} {s : Set β} {n m : ι} {ω : Ω} (h : ∃ j ∈ Set.Icc n m, u j ω ∈ s) :
    stoppedValue u (hitting u s n m) ω ∈ s :=
  by
  simp only [stopped_value, hitting, if_pos h]
  obtain ⟨j, hj₁, hj₂⟩ := h
  have : Inf (Set.Icc n m ∩ { i | u i ω ∈ s }) ∈ Set.Icc n m ∩ { i | u i ω ∈ s } :=
    cinfₛ_mem (Set.nonempty_of_mem ⟨hj₁, hj₂⟩)
  exact this.2
#align measure_theory.stopped_value_hitting_mem MeasureTheory.stoppedValue_hitting_mem

/-- The hitting time of a discrete process with the starting time indexed by a stopping time
is a stopping time. -/
theorem isStoppingTime_hitting_isStoppingTime [ConditionallyCompleteLinearOrder ι]
    [IsWellOrder ι (· < ·)] [Countable ι] [TopologicalSpace ι] [OrderTopology ι]
    [FirstCountableTopology ι] [TopologicalSpace β] [PseudoMetrizableSpace β] [MeasurableSpace β]
    [BorelSpace β] {f : Filtration ι m} {u : ι → Ω → β} {τ : Ω → ι} (hτ : IsStoppingTime f τ)
    {N : ι} (hτbdd : ∀ x, τ x ≤ N) {s : Set β} (hs : MeasurableSet s) (hf : Adapted f u) :
    IsStoppingTime f fun x => hitting u s (τ x) N x :=
  by
  intro n
  have h₁ :
    { x | hitting u s (τ x) N x ≤ n } =
      (⋃ i ≤ n, { x | τ x = i } ∩ { x | hitting u s i N x ≤ n }) ∪
        ⋃ i > n, { x | τ x = i } ∩ { x | hitting u s i N x ≤ n } :=
    by
    ext x
    simp [← exists_or, ← or_and_right, le_or_lt]
  have h₂ : (⋃ i > n, { x | τ x = i } ∩ { x | hitting u s i N x ≤ n }) = ∅ :=
    by
    ext x
    simp only [gt_iff_lt, Set.mem_unionᵢ, Set.mem_inter_iff, Set.mem_setOf_eq, exists_prop,
      Set.mem_empty_iff_false, iff_false_iff, not_exists, not_and, not_le]
    rintro m hm rfl
    exact lt_of_lt_of_le hm (le_hitting (hτbdd _) _)
  rw [h₁, h₂, Set.union_empty]
  exact
    MeasurableSet.unionᵢ fun i =>
      MeasurableSet.unionᵢ fun hi =>
        (f.mono hi _ (hτ.measurable_set_eq i)).inter (hitting_is_stopping_time hf hs n)
#align measure_theory.is_stopping_time_hitting_is_stopping_time MeasureTheory.isStoppingTime_hitting_isStoppingTime

section CompleteLattice

variable [CompleteLattice ι] {u : ι → Ω → β} {s : Set β} {f : Filtration ι m}

theorem hitting_eq_infₛ (ω : Ω) : hitting u s ⊥ ⊤ ω = infₛ { i : ι | u i ω ∈ s } :=
  by
  simp only [hitting, Set.mem_Icc, bot_le, le_top, and_self_iff, exists_true_left, Set.Icc_bot,
    Set.Iic_top, Set.univ_inter, ite_eq_left_iff, not_exists]
  intro h_nmem_s
  symm
  rw [infₛ_eq_top]
  exact fun i hi_mem_s => absurd hi_mem_s (h_nmem_s i)
#align measure_theory.hitting_eq_Inf MeasureTheory.hitting_eq_infₛ

end CompleteLattice

section ConditionallyCompleteLinearOrderBot

variable [ConditionallyCompleteLinearOrderBot ι] [IsWellOrder ι (· < ·)]

variable {u : ι → Ω → β} {s : Set β} {f : Filtration ℕ m}

theorem hitting_bot_le_iff {i n : ι} {ω : Ω} (hx : ∃ j, j ≤ n ∧ u j ω ∈ s) :
    hitting u s ⊥ n ω ≤ i ↔ ∃ j ≤ i, u j ω ∈ s :=
  by
  cases' lt_or_le i n with hi hi
  · rw [hitting_le_iff_of_lt _ hi]
    simp
  · simp only [(hitting_le ω).trans hi, true_iff_iff]
    obtain ⟨j, hj₁, hj₂⟩ := hx
    exact ⟨j, hj₁.trans hi, hj₂⟩
#align measure_theory.hitting_bot_le_iff MeasureTheory.hitting_bot_le_iff

end ConditionallyCompleteLinearOrderBot

end MeasureTheory


import tactic

variables (x y : ℕ)

open nat

theorem Q2a : 1 * x = x ∧ x = x * 1 :=
begin
  split,
  { induction x with d hd,
      refl,
    rw [mul_succ,hd],
  },
  rw [mul_succ, mul_zero, zero_add],
end

-- Dr Lawn does not define z in her problem sheet.
-- Fortunately I can infer the type of z from the context.
variable z : ℕ

theorem Q2b : (x + y) * z = x * z + y * z :=
begin
  induction z with d hd,
    refl,
  rw [mul_succ, hd, mul_succ, mul_succ],
  ac_refl,
end

theorem Q2c : (x * y) * z = x * (y * z) :=
begin
  induction z with d hd,
  { refl },
  { rw [mul_succ, mul_succ, hd, mul_add] }
end

-- Q3 def
def is_pred (x y : ℕ) := x.succ = y

theorem Q3a : ¬ ∃ x : ℕ, is_pred x 0 :=
begin
  intro h,
  cases h with x hx,
  unfold is_pred at hx,
  apply succ_ne_zero x,
  assumption,
end

theorem Q3b : y ≠ 0 → ∃! x, is_pred x y :=
begin
  intro hy,
  cases y,
    exfalso,
    apply hy,
    refl,
  clear hy,
  use y,
  split,
  { dsimp only,
    unfold is_pred,
  },
  intro z,
  dsimp only [is_pred],
  exact succ_inj'.1,
end

def aux : 0 < y → ∃ x, is_pred x y :=
begin
  intro hy,
  cases Q3b _ (ne_of_lt hy).symm with x hx,
  use x,
  exact hx.1,
end

-- definition of pred' is "choose a random d such that succ(d) = n"
noncomputable def pred' : ℕ+ → ℕ := λ nhn, classical.some (aux nhn nhn.2)

theorem pred'_def : ∀ np : ℕ+, is_pred (pred' np) np :=
λ nhn, classical.some_spec (aux nhn nhn.2)

def succ' : ℕ → ℕ+ :=
λ n, ⟨n.succ, zero_lt_succ n⟩

noncomputable definition Q3c : ℕ+ ≃ ℕ :=
{ to_fun := pred',
  inv_fun := succ',
  left_inv := begin
    rintro np,
    have h := pred'_def,
    unfold succ',
    ext, dsimp,
    unfold is_pred at h,
    rw h,
  end,
  right_inv := begin
    intro n,
    unfold succ',
    have h := pred'_def,
    unfold is_pred at h,
    rw ← succ_inj',
    rw h,
    clear h,
    refl,
  end
   }


/-
Copyright (c) 2021 Frédéric Dupuis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Frédéric Dupuis
-/

import analysis.normed_space.basic
import analysis.normed_space.linear_isometry

/-!
# Normed star rings and algebras

A normed star monoid is a `star_add_monoid` endowed with a norm such that the star operation is
isometric.

A C⋆-ring is a normed star monoid that is also a ring and that verifies the stronger
condition `∥x⋆ * x∥ = ∥x∥^2` for all `x`.  If a C⋆-ring is also a star algebra, then it is a
C⋆-algebra.

To get a C⋆-algebra `E` over field `𝕜`, use
`[normed_field 𝕜] [star_ring 𝕜] [normed_ring E] [star_ring E] [cstar_ring E]
 [normed_algebra 𝕜 E] [star_module 𝕜 E]`.

## TODO

- Show that `∥x⋆ * x∥ = ∥x∥^2` is equivalent to `∥x⋆ * x∥ = ∥x⋆∥ * ∥x∥`, which is used as the
  definition of C*-algebras in some sources (e.g. Wikipedia).

-/

local postfix `⋆`:1000 := star

/-- A normed star ring is a star ring endowed with a norm such that `star` is isometric. -/
class normed_star_monoid (E : Type*) [normed_group E] [star_add_monoid E] :=
(norm_star : ∀ {x : E}, ∥x⋆∥ = ∥x∥)

export normed_star_monoid (norm_star)
attribute [simp] norm_star

/-- A C*-ring is a normed star ring that satifies the stronger condition `∥x⋆ * x∥ = ∥x∥^2`
for every `x`. -/
class cstar_ring (E : Type*) [normed_ring E] [star_ring E] :=
(norm_star_mul_self : ∀ {x : E}, ∥x⋆ * x∥ = ∥x∥ * ∥x∥)

noncomputable instance : cstar_ring ℝ :=
{ norm_star_mul_self := λ x, by simp only [star, id.def, normed_field.norm_mul] }

variables {𝕜 E : Type*}

open cstar_ring

/-- In a C*-ring, star preserves the norm. -/
@[priority 100] -- see Note [lower instance priority]
instance cstar_ring.to_normed_star_monoid {E : Type*} [normed_ring E] [star_ring E] [cstar_ring E] :
  normed_star_monoid E :=
⟨begin
  intro x,
  by_cases htriv : x = 0,
  { simp only [htriv, star_zero] },
  { have hnt : 0 < ∥x∥ := norm_pos_iff.mpr htriv,
    have hnt_star : 0 < ∥x⋆∥ :=
      norm_pos_iff.mpr ((add_equiv.map_ne_zero_iff star_add_equiv).mpr htriv),
    have h₁ := calc
      ∥x∥ * ∥x∥ = ∥x⋆ * x∥        : norm_star_mul_self.symm
            ... ≤ ∥x⋆∥ * ∥x∥      : norm_mul_le _ _,
    have h₂ := calc
      ∥x⋆∥ * ∥x⋆∥ = ∥x * x⋆∥      : by rw [←norm_star_mul_self, star_star]
             ... ≤ ∥x∥ * ∥x⋆∥     : norm_mul_le _ _,
    exact le_antisymm (le_of_mul_le_mul_right h₂ hnt_star) (le_of_mul_le_mul_right h₁ hnt) },
end⟩

lemma cstar_ring.norm_self_mul_star [normed_ring E] [star_ring E] [cstar_ring E] {x : E} :
  ∥x * x⋆∥ = ∥x∥ * ∥x∥ :=
by { nth_rewrite 0 [←star_star x], simp only [norm_star_mul_self, norm_star] }

lemma cstar_ring.norm_star_mul_self' [normed_ring E] [star_ring E] [cstar_ring E] {x : E} :
  ∥x⋆ * x∥ = ∥x⋆∥ * ∥x∥ :=
by rw [norm_star_mul_self, norm_star]

section starₗᵢ

variables [comm_semiring 𝕜] [star_ring 𝕜] [normed_ring E] [star_ring E] [normed_star_monoid E]
variables [module 𝕜 E] [star_module 𝕜 E]

variables (𝕜)
/-- `star` bundled as a linear isometric equivalence -/
def starₗᵢ : E ≃ₗᵢ⋆[𝕜] E :=
{ map_smul' := star_smul,
  norm_map' := λ x, norm_star,
  .. star_add_equiv }

variables {𝕜}

@[simp] lemma coe_starₗᵢ : (starₗᵢ 𝕜 : E → E) = star := rfl

lemma starₗᵢ_apply {x : E} : starₗᵢ 𝕜 x = star x := rfl

end starₗᵢ


example (x y : ℕ) : (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
  by rw [mul_add, add_mul, add_mul, ←add_assoc]

example (x y : ℕ) : (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
  by simp [mul_add, add_mul, add_left_comm]
-- lean 3.6 removes simp attributes from add_left_comm
-- i.e., now it is required to provide that lemma to solve this example.


/-
Copyright (c) 2016 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad

! This file was ported from Lean 3 source module data.int.dvd.pow
! leanprover-community/mathlib commit b3f25363ae62cb169e72cd6b8b1ac97bacf21ca7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Int.Dvd.Basic
import Mathlib.Data.Nat.Pow

/-!
# Basic lemmas about the divisibility relation in `ℤ` involving powers.
-/

open Nat

namespace Int

set_option linter.deprecated false in
@[simp]
theorem sign_pow_bit1 (k : ℕ) : ∀ n : ℤ, n.sign ^ bit1 k = n.sign
  | (_ + 1 : ℕ) => one_pow (bit1 k)
  | 0 => zero_pow (Nat.zero_lt_bit1 k)
  | -[_+1] => (neg_pow_bit1 1 k).trans (congr_arg (fun x => -x) (one_pow (bit1 k)))
#align int.sign_pow_bit1 Int.sign_pow_bit1

theorem pow_dvd_of_le_of_pow_dvd {p m n : ℕ} {k : ℤ} (hmn : m ≤ n) (hdiv : ↑(p ^ n) ∣ k) :
    ↑(p ^ m) ∣ k := by
  induction k with
  | ofNat k =>
    apply Int.coe_nat_dvd.2
    apply Nat.pow_dvd_of_le_of_pow_dvd hmn
    apply Int.coe_nat_dvd.1 hdiv
  | negSucc k =>
    show ↑(p ^ m) ∣ -(↑(k + 1) : ℤ)
    apply dvd_neg_of_dvd
    apply Int.coe_nat_dvd.2
    apply Nat.pow_dvd_of_le_of_pow_dvd hmn
    apply Int.coe_nat_dvd.1
    apply dvd_of_dvd_neg
    exact hdiv
#align int.pow_dvd_of_le_of_pow_dvd Int.pow_dvd_of_le_of_pow_dvd

theorem dvd_of_pow_dvd {p k : ℕ} {m : ℤ} (hk : 1 ≤ k) (hpk : ↑(p ^ k) ∣ m) : ↑p ∣ m := by
  rw [← pow_one p] ; exact pow_dvd_of_le_of_pow_dvd hk hpk
#align int.dvd_of_pow_dvd Int.dvd_of_pow_dvd

end Int


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Patrick Stevens
-/
import data.nat.choose.basic
import data.nat.prime

/-!
# Divisibility properties of binomial coefficients
-/

namespace nat

open_locale nat

namespace prime

lemma dvd_choose_add {p a b : ℕ} (hap : a < p) (hbp : b < p) (h : p ≤ a + b)
  (hp : prime p) : p ∣ choose (a + b) a :=
have h₁ : p ∣ (a + b)!, from hp.dvd_factorial.2 h,
have h₂ : ¬p ∣ a!, from mt hp.dvd_factorial.1 (not_le_of_gt hap),
have h₃ : ¬p ∣ b!, from mt hp.dvd_factorial.1 (not_le_of_gt hbp),
by
  rw [← choose_mul_factorial_mul_factorial (le.intro rfl), mul_assoc, hp.dvd_mul, hp.dvd_mul,
      add_tsub_cancel_left a b] at h₁;
  exact h₁.resolve_right (not_or_distrib.2 ⟨h₂, h₃⟩)

lemma dvd_choose_self {p k : ℕ} (hk : 0 < k) (hkp : k < p) (hp : prime p) :
  p ∣ choose p k :=
begin
  have r : k + (p - k) = p,
    by rw [← add_tsub_assoc_of_le (nat.le_of_lt hkp) k, add_tsub_cancel_left],
  have e : p ∣ choose (k + (p - k)) k,
    by exact dvd_choose_add hkp (nat.sub_lt (hk.trans hkp) hk) (by rw r) hp,
  rwa r at e,
end

end prime

end nat


import data.real.basic

--- rising and falling factorials
def falling_qfact (q: ℝ) (n k: ℕ) : ℝ :=
finset.prod (finset.range k) (λ i, 1 - q^(n-i))

def rising_qfact (q: ℝ) (k: ℕ) : ℝ :=
finset.prod (finset.range k) (λ i, 1 - q^(i+1))

--- q-binomial coefficient
noncomputable def qbinom (q: ℝ) (n k: ℕ) : ℝ := (falling_qfact q n k) / (rising_qfact q k)

--- Some lemmas for splitting up the problem
lemma q_pow_n_ne_one (q: ℝ) (n: ℕ) (qpos: q ≥ 0) (qn1: q ≠ 1) (npos: 0 < n) :
q^n ≠ 1 :=
begin
  cases ne.lt_or_lt qn1,
  have q_pow_lt_one := pow_lt_pow_of_lt_left h qpos npos,
  rw one_pow at q_pow_lt_one,
  exact ne_of_lt q_pow_lt_one,

  have q_pow_lt_one := pow_lt_pow_of_lt_left h zero_le_one npos,
  rw one_pow at q_pow_lt_one,
  exact ne_of_gt q_pow_lt_one,
end

lemma one_minus_q_pow_n_ne_zero (q: ℝ) (n: ℕ) (qpos: q ≥ 0) (qn1: q ≠ 1) (npos: 0 < n) :
1 - q^n ≠ 0 :=
begin
  have q_pow_n_ne_one := q_pow_n_ne_one q n qpos qn1 npos,
  rw ne_comm at q_pow_n_ne_one,
  exact sub_ne_zero_of_ne q_pow_n_ne_one,
end

lemma q_expand_one (q: ℝ) (n k: ℕ) (qpos: q ≥ 0) (qn1: q ≠ 1) (npos: 0 < n) (kln: k ≤ n) :
(1 : ℝ) = q^k * (1 - q^(n-k)) / (1 - q^n) + (1 - q^k) / (1 - q^n) :=
begin
  have denom_nonzero := one_minus_q_pow_n_ne_zero q n qpos qn1 npos,
  symmetry,
  rw div_eq_mul_inv,
  rw div_eq_mul_inv,
  rw ← right_distrib,
  rw sub_eq_neg_add,
  rw left_distrib,
  rw ← neg_mul_eq_mul_neg,
  rw ← pow_add,
  rw nat.add_sub_of_le,
  simp,
  rw ← sub_eq_neg_add,
  rw mul_inv_cancel denom_nonzero,
  exact kln,
end

lemma rising_fact_rec2 (q: ℝ) (n k: ℕ) (qpos: q ≥ 0) (qn1: q ≠ 1) (npos: 0 < n) (kpos: 0 < k) (kln: k ≤ n) :
(1 - q ^ (n - k)) / (1 - q ^ n) * (falling_qfact q n k) = (falling_qfact q (n-1) k) :=
begin
  have denom_nonzero := one_minus_q_pow_n_ne_zero q n qpos qn1 npos,
  rw falling_qfact,
  rw mul_comm,
  rw div_eq_mul_inv,
  rw ← mul_assoc,
  rw ← finset.prod_range_succ,
  rw finset.prod_range_succ',
  rw nat.sub_zero,
  rw mul_assoc,
  rw mul_inv_cancel denom_nonzero,
  simp,
  conv in (λ (i : ℕ), 1 - q ^ (n - (i + 1))) {
    rw add_comm,
    rw ← nat.sub_sub,
  },
  rw ← falling_qfact,
end

lemma falling_fact_rec (q: ℝ) (n k: ℕ) (qpos: q ≥ 0) (qn1: q ≠ 1) (npos: 0 < n) (kpos: 0 < k) (kln: k ≤ n) :
(falling_qfact q n k) = (1 - q ^ n) * (falling_qfact q (n-1) (k-1)) :=
begin
  symmetry,
  rw falling_qfact,
  rw mul_comm,
  conv in (λ (i : ℕ), 1 - q ^ (n - 1 - i)) {
    rw nat.sub_sub,
    rw add_comm,
  },
  conv in (1 - q ^ n) { rw ← nat.sub_zero n },
  rw ← finset.prod_range_succ' (λ (i : ℕ), 1 - q ^ (n - i)),
  rw ← falling_qfact,
  rw nat.sub_add_cancel,
  exact nat.succ_le_of_lt kpos,
end

lemma rising_fact_rec (q: ℝ) (k: ℕ) (qpos: q ≥ 0) (qn1: q ≠ 1) (kpos: 0 < k):
(rising_qfact q k) = (1 - q ^ k) * (rising_qfact q (k-1)) :=
begin
  symmetry,
  rw rising_qfact,
  conv in (1 - q^k) { rw ← nat.sub_add_cancel (nat.succ_le_of_lt kpos), },
  rw mul_comm,
  rw ← finset.prod_range_succ,
  rw nat.sub_add_cancel,
  rw ← rising_qfact,
  exact nat.succ_le_of_lt kpos,
end

--- Recurrence for q-binomial coefficients
theorem qbinom_recurrence (q: ℝ) (n k: ℕ) (qpos: q ≥ 0) (qn1: q ≠ 1) (npos: 0 < n) (kpos: 0 < k) (kln: k ≤ n) :
qbinom q n k = q^k * qbinom q (n-1) k + qbinom q (n-1) (k-1) :=
begin
  -- rw qbinom, rw falling_qfact, rw rising_qfact,  -- Unwrap left side
  rw ← one_mul (qbinom q n k),
  rw q_expand_one q n k qpos qn1 npos kln,
  rw right_distrib,
  rw qbinom,
  rw mul_comm (q^k),
  rw ← div_mul_eq_mul_div,
  rw mul_comm _ (q^k),
  rw mul_assoc,
  rw mul_comm ((1 - q ^ (n - k)) / (1 - q ^ n)),
  rw div_mul_eq_mul_div _ _ (rising_qfact q k),
  rw mul_comm (falling_qfact q n k),
  rw (rising_fact_rec2 q n k qpos qn1 npos kpos kln),
  rw ← qbinom,

  rw add_comm,
  rw (falling_fact_rec q n k qpos qn1 npos kpos kln),
  rw (rising_fact_rec q k qpos qn1 kpos),
  rw ← div_mul_div,
  rw ← qbinom,
  rw ← mul_assoc,
  rw div_mul_div,
  rw mul_comm (1 - q ^ k),
  rw div_self,
  simp,
  rw add_comm,

  -- Make sure we can do div_self
  have q_pos_n_ne_zero := one_minus_q_pow_n_ne_zero q n qpos qn1 npos,
  have q_pos_k_ne_zero := one_minus_q_pow_n_ne_zero q k qpos qn1 kpos,
  exact (mul_ne_zero q_pos_n_ne_zero q_pos_k_ne_zero),
end


/-
Copyright (c) 2023 Mark Andrew Gerads. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mark Andrew Gerads, Junyan Xu, Eric Wieser
-/
import tactic.ring
import data.nat.parity

/-!
# Hyperoperation sequence

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

This file defines the Hyperoperation sequence.
`hyperoperation 0 m k = k + 1`
`hyperoperation 1 m k = m + k`
`hyperoperation 2 m k = m * k`
`hyperoperation 3 m k = m ^ k`
`hyperoperation (n + 3) m 0 = 1`
`hyperoperation (n + 1) m (k + 1) = hyperoperation n m (hyperoperation (n + 1) m k)`

## References

* <https://en.wikipedia.org/wiki/Hyperoperation>

## Tags

hyperoperation
-/

/--
Implementation of the hyperoperation sequence
where `hyperoperation n m k` is the `n`th hyperoperation between `m` and `k`.
-/
def hyperoperation : ℕ → ℕ → ℕ → ℕ
| 0 _ k := k + 1
| 1 m 0 := m
| 2 _ 0 := 0
| (n + 3) _ 0 := 1
| (n + 1) m (k + 1) := hyperoperation n m (hyperoperation (n + 1) m k)

-- Basic hyperoperation lemmas

@[simp] lemma hyperoperation_zero (m : ℕ) : hyperoperation 0 m = nat.succ :=
funext $ λ k, by rw [hyperoperation, nat.succ_eq_add_one]

lemma hyperoperation_ge_three_eq_one (n m : ℕ) : hyperoperation (n + 3) m 0 = 1 :=
by rw hyperoperation

lemma hyperoperation_recursion (n m k : ℕ) :
  hyperoperation (n + 1) m (k + 1) = hyperoperation n m (hyperoperation (n + 1) m k) :=
by obtain (_|_|_) := n; rw hyperoperation

-- Interesting hyperoperation lemmas

@[simp] lemma hyperoperation_one : hyperoperation 1 = (+) :=
begin
  ext m k,
  induction k with bn bih,
  { rw [nat_add_zero m, hyperoperation], },
  { rw [hyperoperation_recursion, bih, hyperoperation_zero],
    exact nat.add_assoc m bn 1, },
end

@[simp] lemma hyperoperation_two : hyperoperation 2 = (*) :=
begin
  ext m k,
  induction k with bn bih,
  { rw hyperoperation,
    exact (nat.mul_zero m).symm, },
  { rw [hyperoperation_recursion, hyperoperation_one, bih],
    ring, },
end

@[simp] lemma hyperoperation_three : hyperoperation 3 = (^) :=
begin
  ext m k,
  induction k with bn bih,
  { rw hyperoperation_ge_three_eq_one,
    exact (pow_zero m).symm, },
  { rw [hyperoperation_recursion, hyperoperation_two, bih],
    exact (pow_succ m bn).symm, },
end

lemma hyperoperation_ge_two_eq_self (n m : ℕ) : hyperoperation (n + 2) m 1 = m :=
begin
  induction n with nn nih,
  { rw hyperoperation_two,
    ring, },
  { rw [hyperoperation_recursion, hyperoperation_ge_three_eq_one, nih], },
end

lemma hyperoperation_two_two_eq_four (n : ℕ) : hyperoperation (n + 1) 2 2 = 4 :=
begin
  induction n with nn nih,
  { rw hyperoperation_one, },
  { rw [hyperoperation_recursion, hyperoperation_ge_two_eq_self, nih], },
end

lemma hyperoperation_ge_three_one (n : ℕ) : ∀ (k : ℕ), hyperoperation (n + 3) 1 k = 1 :=
begin
  induction n with nn nih,
  { intros k,
    rw [hyperoperation_three, one_pow], },
  { intros k,
    cases k,
    { rw hyperoperation_ge_three_eq_one, },
    { rw [hyperoperation_recursion, nih], }, },
end

lemma hyperoperation_ge_four_zero (n k : ℕ) :
  hyperoperation (n + 4) 0 k = if (even k) then 1 else 0 :=
begin
  induction k with kk kih,
  { rw hyperoperation_ge_three_eq_one,
    simp only [even_zero, if_true], },
  { rw hyperoperation_recursion,
    rw kih,
    simp_rw nat.even_add_one,
    split_ifs,
    { exact hyperoperation_ge_two_eq_self (n + 1) 0, },
    { exact hyperoperation_ge_three_eq_one n 0, }, },
end


variable (α : Type)

variable (A B : α → Prop)

variable (C : α → α → Prop)

theorem totally_fake (A : Prop) : A := sorry

#check totally_fake

-- ∀ \forall for universal quantifier

example (h : ∀ (x:α), A x) (a : α) : A a := h a

example : ∀ x, A x → A x := fun (x:α) (h: A x) => h

-- ∃ \exists for existential quantifier

#check @Exists α

#check @Exists.intro

#check @Exists.elim

-- = equality

#check @Eq α

#check @Eq.refl α

#check @Eq.symm α

#check @Eq.trans α

#check @Eq.subst α A

variable (f: α → β)
variable (x y : α)
variable (h : x = y)

#check @congrArg α β x y f

#check congrArg f h


/-
Copyright (c) 2020 Alexander Bentkamp, Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alexander Bentkamp, Sébastien Gouëzel
-/
import data.complex.basic
import algebra.algebra.ordered
import data.matrix.notation
import field_theory.tower
import linear_algebra.finite_dimensional

/-!
# Complex number as a vector space over `ℝ`

This file contains the following instances:
* Any `•`-structure (`has_scalar`, `mul_action`, `distrib_mul_action`, `module`, `algebra`) on
  `ℝ` imbues a corresponding structure on `ℂ`. This includes the statement that `ℂ` is an `ℝ`
  algebra.
* any complex vector space is a real vector space;
* any finite dimensional complex vector space is a finite dimensional real vector space;
* the space of `ℝ`-linear maps from a real vector space to a complex vector space is a complex
  vector space.

It also defines three linear maps:

* `complex.re_lm`;
* `complex.im_lm`;
* `complex.of_real_lm`;
* `complex.conj_lm`.

They are bundled versions of the real part, the imaginary part, the embedding of `ℝ` in `ℂ`, and
the complex conjugate as `ℝ`-linear maps.
-/
noncomputable theory

namespace complex

variables {R : Type*} {S : Type*}

section

variables [has_scalar R ℝ]

/- The useless `0` multiplication in `smul` is to make sure that
`restrict_scalars.module ℝ ℂ ℂ  = complex.module` definitionally. -/
instance : has_scalar R ℂ :=
{ smul := λ r x, ⟨r • x.re - 0 * x.im, r • x.im + 0 * x.re⟩ }

lemma smul_re (r : R) (z : ℂ) : (r • z).re = r • z.re := by simp [(•)]
lemma smul_im (r : R) (z : ℂ) : (r • z).im = r • z.im := by simp [(•)]

@[simp] lemma smul_coe {x : ℝ} {z : ℂ} : x • z = x * z :=
by ext; simp [smul_re, smul_im]

end

instance [has_scalar R ℝ] [has_scalar S ℝ] [smul_comm_class R S ℝ] : smul_comm_class R S ℂ :=
{ smul_comm := λ r s x, by ext; simp [smul_re, smul_im, smul_comm] }

instance [has_scalar R S] [has_scalar R ℝ] [has_scalar S ℝ] [is_scalar_tower R S ℝ] :
  is_scalar_tower R S ℂ :=
{ smul_assoc := λ r s x, by ext; simp [smul_re, smul_im, smul_assoc] }

instance [monoid R] [mul_action R ℝ] : mul_action R ℂ :=
{ one_smul := λ x, by ext; simp [smul_re, smul_im, one_smul],
  mul_smul := λ r s x, by ext; simp  [smul_re, smul_im, mul_smul] }

instance [semiring R] [distrib_mul_action R ℝ] : distrib_mul_action R ℂ :=
{ smul_add := λ r x y, by ext; simp [smul_re, smul_im, smul_add],
  smul_zero := λ r, by ext; simp [smul_re, smul_im, smul_zero] }

instance [semiring R] [module R ℝ] : module R ℂ :=
{ add_smul := λ r s x, by ext; simp [smul_re, smul_im, add_smul],
  zero_smul := λ r, by ext; simp [smul_re, smul_im, zero_smul] }

instance [comm_semiring R] [algebra R ℝ] : algebra R ℂ :=
{ smul := (•),
  smul_def' := λ r x, by ext; simp [smul_re, smul_im, algebra.smul_def],
  commutes' := λ r ⟨xr, xi⟩, by ext; simp [smul_re, smul_im, algebra.commutes],
  ..complex.of_real.comp (algebra_map R ℝ) }

/-- Complex conjugation as an `ℝ`-algebra isomorphism  -/
def conj_alg_equiv : ℂ ≃ₐ[ℝ] ℂ :=
{ inv_fun := complex.conj,
  left_inv := complex.conj_conj,
  right_inv := complex.conj_conj,
  commutes' := complex.conj_of_real,
  .. complex.conj }

section
open_locale complex_order

lemma complex_ordered_module : ordered_module ℝ ℂ :=
{ smul_lt_smul_of_pos := λ z w x h₁ h₂,
  begin
    obtain ⟨y, l, rfl⟩ := lt_def.mp h₁,
    refine lt_def.mpr ⟨x * y, _, _⟩,
    exact mul_pos h₂ l,
    ext; simp [mul_add],
  end,
  lt_of_smul_lt_smul_of_pos := λ z w x h₁ h₂,
  begin
    obtain ⟨y, l, e⟩ := lt_def.mp h₁,
    by_cases h : x = 0,
    { subst h, exfalso, apply lt_irrefl 0 h₂, },
    { refine lt_def.mpr ⟨y / x, div_pos l h₂, _⟩,
      replace e := congr_arg (λ z, (x⁻¹ : ℂ) * z) e,
      simp only [mul_add, ←mul_assoc, h, one_mul, of_real_eq_zero, smul_coe, ne.def,
        not_false_iff, inv_mul_cancel] at e,
      convert e,
      simp only [div_eq_iff_mul_eq, h, of_real_eq_zero, of_real_div, ne.def, not_false_iff],
      norm_cast,
      simp [mul_comm _ y, mul_assoc, h],
    },
  end }

localized "attribute [instance] complex_ordered_module" in complex_order

end

@[simp] lemma coe_algebra_map : ⇑(algebra_map ℝ ℂ) = complex.of_real := rfl

open submodule finite_dimensional

lemma is_basis_one_I : is_basis ℝ ![1, I] :=
begin
  refine ⟨linear_independent_fin2.2 ⟨by simp [I_ne_zero], λ a, mt (congr_arg re) $ by simp⟩,
    eq_top_iff'.2 $ λ z, _⟩,
  suffices : ∃ a b : ℝ, z = a • I + b • 1,
    by simpa [mem_span_insert, mem_span_singleton, -set.singleton_one],
  use [z.im, z.re],
  simp [algebra.smul_def, add_comm]
end

instance : finite_dimensional ℝ ℂ := of_fintype_basis is_basis_one_I

@[simp] lemma finrank_real_complex : finite_dimensional.finrank ℝ ℂ = 2 :=
by rw [finrank_eq_card_basis is_basis_one_I, fintype.card_fin]

@[simp] lemma dim_real_complex : module.rank ℝ ℂ = 2 :=
by simp [← finrank_eq_dim, finrank_real_complex]

lemma {u} dim_real_complex' : cardinal.lift.{0 u} (module.rank ℝ ℂ) = 2 :=
by simp [← finrank_eq_dim, finrank_real_complex, bit0]

/-- `fact` version of the dimension of `ℂ` over `ℝ`, locally useful in the definition of the
circle. -/
lemma finrank_real_complex_fact : fact (finrank ℝ ℂ = 2) := ⟨finrank_real_complex⟩

end complex

/- Register as an instance (with low priority) the fact that a complex vector space is also a real
vector space. -/
@[priority 900]
instance module.complex_to_real (E : Type*) [add_comm_group E] [module ℂ E] : module ℝ E :=
restrict_scalars.module ℝ ℂ E

instance module.real_complex_tower (E : Type*) [add_comm_group E] [module ℂ E] :
  is_scalar_tower ℝ ℂ E :=
restrict_scalars.is_scalar_tower ℝ ℂ E

@[priority 100]
instance finite_dimensional.complex_to_real (E : Type*) [add_comm_group E] [module ℂ E]
  [finite_dimensional ℂ E] : finite_dimensional ℝ E :=
finite_dimensional.trans ℝ ℂ E

lemma dim_real_of_complex (E : Type*) [add_comm_group E] [module ℂ E] :
  module.rank ℝ E = 2 * module.rank ℂ E :=
cardinal.lift_inj.1 $
  by { rw [← dim_mul_dim' ℝ ℂ E, complex.dim_real_complex], simp [bit0] }

lemma finrank_real_of_complex (E : Type*) [add_comm_group E] [module ℂ E] :
  finite_dimensional.finrank ℝ E = 2 * finite_dimensional.finrank ℂ E :=
by rw [← finite_dimensional.finrank_mul_finrank ℝ ℂ E, complex.finrank_real_complex]

namespace complex

/-- Linear map version of the real part function, from `ℂ` to `ℝ`. -/
def re_lm : ℂ →ₗ[ℝ] ℝ :=
{ to_fun := λx, x.re,
  map_add' := add_re,
  map_smul' := by simp, }

@[simp] lemma re_lm_coe : ⇑re_lm = re := rfl

/-- Linear map version of the imaginary part function, from `ℂ` to `ℝ`. -/
def im_lm : ℂ →ₗ[ℝ] ℝ :=
{ to_fun := λx, x.im,
  map_add' := add_im,
  map_smul' := by simp, }

@[simp] lemma im_lm_coe : ⇑im_lm = im := rfl

/-- Linear map version of the canonical embedding of `ℝ` in `ℂ`. -/
def of_real_lm : ℝ →ₗ[ℝ] ℂ :=
{ to_fun := coe,
  map_add' := of_real_add,
  map_smul' := λc x, by simp [algebra.smul_def] }

@[simp] lemma of_real_lm_coe : ⇑of_real_lm = coe := rfl

/-- `ℝ`-linear map version of the complex conjugation function from `ℂ` to `ℂ`. -/
def conj_lm : ℂ →ₗ[ℝ] ℂ :=
{ map_smul' := by simp [restrict_scalars_smul_def],
  ..conj }

@[simp] lemma conj_lm_coe : ⇑conj_lm = conj := rfl

end complex


-- ----------------------------------
-- Demostrar que
--    (s ∩ t) ∪ (s ∩ u) ⊆ s ∩ (t ∪ u)
-- ----------------------------------

import data.set.basic
open set

variable {α : Type}
variables s t u : set α

example : (s ∩ t) ∪ (s ∩ u) ⊆ s ∩ (t ∪ u):=
sorry


import data.fintype.basic
import algebra.group.defs
import algebra.hom.group
import group_theory.subgroup.basic
import group_theory.order_of_element
import tactic

variables {G : Type*} [group G] [fintype G]

variable (G)
def cubedistr_prop : Prop := ∀ (a b : G), (a * b) ^ 3 = a ^ 3 * b ^ 3

variable {G}

lemma pow_three (a : G) : a ^ 3 = a * a * a := by group

lemma sq_antihom (cubedistr : cubedistr_prop G) (a b : G) :
  (a * b) ^ 2 = b ^ 2 * a ^ 2 :=
calc (a * b) ^ 2 = a * b * a * b : by {rw pow_two, group}
... = b⁻¹ * ((b * a) * (b * a) * (b * a)) * a⁻¹ : by group
... = b⁻¹ * (b * a) ^ 3 * a⁻¹ : by rw pow_three
... = b⁻¹ * (b ^ 3 * a ^ 3) * a⁻¹ : by rw cubedistr
... = b ^ 2 * a ^ 2 : by group

lemma sqcube_comm (cubedistr : cubedistr_prop G) (a b : G) :
  a ^ 2 * b ^ 3 = b ^ 3 * a ^ 2 :=
calc a ^ 2 * b ^ 3 = a ^ 2 * b ^ 2 * b * a * a⁻¹ : by group
... = (b * a) * (b * a) * (b * a) * a⁻¹ : by {rw [←sq_antihom cubedistr, pow_two], group}
... = (b * a) ^ 3 * a⁻¹ : by rw pow_three
... = b ^ 3 * a ^ 3 * a⁻¹ : by rw cubedistr
... = b ^ 3 * a ^ 2 : by group

-- If G is a finite group such that (xy)^3 = x^3y^3 and 3 ∤ |G|, then G is abelian.
theorem comm_of_cubehom_group (cubedistr : cubedistr_prop G)
  (hord : ¬3 ∣ fintype.card G) :
  ∀ (a b : G), a * b = b * a :=
begin
  set cubehom : G →* G := {
    to_fun := λ x, x ^ 3,
    map_one' := by group,
    map_mul' := cubedistr,
  } with hcubehom,

  have hinj : function.injective ⇑cubehom := by {
    simp_rw [←monoid_hom.ker_eq_bot_iff, subgroup.eq_bot_iff_forall,
      monoid_hom.mem_ker, hcubehom],
    intros x hx,
    by_contra h,
    exact hord (order_of_eq_prime hx h ▸ order_of_dvd_card_univ),
  },
  have hsur : function.surjective ⇑cubehom := fintype.injective_iff_surjective.mp hinj,

  have sqcomm' : ∀ (a b : G), a ^ 2 * b = b * a ^ 2 := by {
    intros a b,
    obtain ⟨t, ht⟩ := hsur b,
    dsimp [hcubehom] at ht, subst ht,
    exact sqcube_comm cubedistr a t,
  },

  have sqcomm : ∀ (a b : G), a ^ 2 * b ^ 2 = b ^ 2 * a ^ 2 :=
    λ a b, calc a ^ 2 * b ^ 2 = a ^ 2 * b * b : by group
    ... = b * a ^ 2 * b : by rw sqcomm'
    ... = b * (a ^ 2 * b) : mul_assoc _ _ _
    ... = b * (b * a ^ 2) : by rw sqcomm'
    ... = b ^ 2 * a ^ 2 : by group,

  intros a b,
  calc a * b = a⁻¹ * (a ^ 2 * b ^ 2) * b⁻¹ : by group
  ... = a⁻¹ * (b ^ 2 * a ^ 2) * b⁻¹ : by rw sqcomm
  ... = a⁻¹ * (a * b) ^ 2 * b⁻¹ : by rw sq_antihom cubedistr
  ... = a⁻¹ * a * b * a * b * b⁻¹ : by {rw pow_two, group}
  ... = b * a : by group
end


-- import the definition of the gcd maze
import mazes.gcd_maze.definition
import data.int.gcd
open maze direction

/-

# Prove Bezout's Theorem

You are in a maze of integers, all distinct.

You can go north, south east or west.

North adds `a` to your integer, South subtracts `a`.
East adds `b` to your integer, West subtracts `b`.
You start at 0. The exit is at `nat.gcd a b`.
Can you prove you can always exit?

Solver remark : there are infinitely many mazes.
-/

/- Lemma : no-side-bar
Can you prove you can escape in the general case?
-/
theorem challenge (A B : ℕ) : can_escape A B 0 :=
begin
  have solution : can_escape A B (nat.gcd A B),
    out,
  have hs : ∀ t l : ℕ, can_escape A B t → can_escape A B (t + l*A),
  { intros t l,
    induction l with d hd,
    { intro h, convert h, simp },
    intro h,
    specialize hd h,
    s,
    convert hd,
    ring
  },
  have hns : ∀ t : ℤ, ∀ l : ℤ, can_escape A B t → can_escape A B (t + l * A),
  { intros t l,
    apply int.induction_on l; clear l,
    { intro h, convert h, ring},
    { intros d hd,
      intro h, specialize hd h,
      s,
      convert hd,
      ring,
    },
    { intros d hd,
      intro h, specialize hd h,
      n,
      convert hd using 1,
      ring,
    } },
  have hew : ∀ t : ℤ, ∀ m : ℤ, can_escape A B t → can_escape A B (t + m * B),
  { intros t l,
    apply int.induction_on l; clear l,
    { intro h, convert h, ring},
    { intros d hd,
      intro h, specialize hd h,
      w,
      convert hd,
      ring,
    },
    { intros d hd,
      intro h, specialize hd h,
      e,
      convert hd using 1,
      ring,
    } },
  suffices : ∃ L M : ℤ, L * A + M * B = nat.gcd A B,
  { rcases this with ⟨L, M, h⟩,
    rw ← h at solution,
    specialize hew _ (-M) solution,
    simp at hew,
    specialize hns _ (-L) hew,
    convert hns,
    ring,
  },
  have h := nat.gcd_eq_gcd_ab A B,
  rw h,
  use [A.gcd_a B, A.gcd_b B],
  ring,
end


/-
Copyright (c) 2018 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison
-/
import tactic.suggest
import ring_theory.principal_ideal_domain
import ring_theory.polynomial

open_locale polynomial
/- Turn off trace messages so they don't pollute the test build: -/
set_option trace.silence_library_search true

example {α : Type} [euclidean_domain α] {S : ideal α} {x y : α} (hy : y ∈ S) : x % y ∈ S ↔ x ∈ S :=
by library_search -- exact mod_mem_iff hy

variables {R : Type} [comm_ring R] [decidable_eq R]
variables {I : ideal R[X]}

example {m n : ℕ} (H : m ≤ n) : I.leading_coeff_nth m ≤ I.leading_coeff_nth n :=
by library_search -- exact ideal.leading_coeff_nth_mono I H


/-
Copyright (c) 2022 Henrik Böving. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/

namespace Cpdt
namespace Chapter4

inductive Even : Nat → Prop where
  | zero : Even 0
  | succSucc (n : Nat) : Even n → Even n.succ.succ

theorem even4 : Even 4 := by
  simp[Even.succSucc, Even.zero]

theorem even_3_contra : Even 3 → False := by
  intro h
  cases h with
  | succSucc _ h => cases h

theorem even_add : Even n → Even m → Even (n + m) := by
  intro h1
  induction h1 with
  | zero =>
    intro h
    simp_arith
    assumption
  | succSucc x xh ih =>
    intro h
    simp_arith
    constructor
    simp_arith
    apply ih
    assumption

theorem even_contra_helper : ∀ n', Even n' → ∀ n, n' ≠ Nat.succ (n + n) := by
  intro n' hn'
  induction hn' with
  | zero => simp_all
  | succSucc x hx ih =>
    intro n hn
    cases x <;> cases n <;> simp_all <;> simp_arith at *
    case succSucc.succ.succ y m =>
      apply ih m
      assumption

theorem even_contra : Even (Nat.succ (n + n)) → False := by
  intro h
  apply even_contra_helper
  assumption
  rfl

end Chapter4
end Cpdt


import float.basic
import float.round

open float

variable (prec : ℕ)

def divl (x y : 𝔽) : 𝔽 :=
round_down prec (eval x / eval y)

def divr (x y : 𝔽) : 𝔽 :=
round_up prec (eval x / eval y)

meta def div_rat' (x y : 𝔽) : ℚ :=
let x' := quot.unquot x, y' := quot.unquot y in
if x'.e ≤ y'.e
then rat.mk (x'.m) (y'.m * 2 ^ int.to_nat (y'.e - x'.e))
else rat.mk (x'.m * 2 ^ int.to_nat (x'.e - y'.e)) (y'.m)

meta def divl' (x y : 𝔽) : 𝔽 :=
round_down prec (div_rat' x y)

meta def divr' (x y : 𝔽) : 𝔽 :=
round_up prec (div_rat' x y)

--instance : has_div 𝔽 := ⟨divl 10⟩ -- Fixed precision for now
meta instance : has_div 𝔽 := ⟨divl' 10⟩ -- Fixed precision for now

-- Lemmas.

lemma divl_bound (x y : 𝔽)
: eval (divl prec x y) ≤ eval x / eval y :=
begin
  simp [divl, round_down, eval_mk],
  have h : 0 < ((2 : ℚ) ^ prec), { norm_num, },
  rw [mul_inv_le_iff h, mul_comm], exact floor_le _,
end

lemma divr_bound (x y : 𝔽)
: eval x / eval y ≤ eval (divr prec x y) :=
begin
  simp [divr, round_up, eval_mk],
  have h : 0 < ((2 : ℚ) ^ prec), { norm_num, },
  rw [←mul_le_mul_right h, mul_assoc, inv_mul_cancel (ne_of_gt h), mul_one],
  exact le_ceil _,
end

lemma divl_diff_lb (x y : 𝔽)
: 0 ≤ (eval x / eval y) - eval (divl prec x y) :=
sub_nonneg_of_le (divl_bound prec x y)

lemma divl_diff_ub (x y : 𝔽)
: (eval x / eval y) - eval (divl prec x y) ≤ 2 ^ (-(prec : ℤ)) :=
begin
  have h := divr_bound prec x y,
  replace h := sub_le_sub_right h (eval (divl prec x y)),
  apply le_trans h, simp only [divr, divl],
  rw [←eval_sub],
  have h1 : 2 ^ (-(prec : ℤ)) = eval (float.mk 1 (-prec)),
  { simp [eval_mk], },
  rw h1, exact round_up_diff_round_down prec _,
end

-- #eval divl 10 (mk 50 0) (mk 3 2)

-- Isabelle version:
-- lift_definition float_divr :: "nat => float => float => float" is
--   "λ(prec::nat) a b. round_up (prec + ⌊ log 2 ¦b¦ ⌋ - ⌊ log 2 ¦a¦ ⌋) (a / b)" by simp

-- TODO: Precision needs to change?


/-
Copyright (c) 2021 Thomas Browning. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Thomas Browning
-/
import field_theory.abel_ruffini
import analysis.calculus.local_extr
import ring_theory.eisenstein_criterion
/-!
Construction of an algebraic number that is not solvable by radicals.

The main ingredients are:
 * `solvable_by_rad.is_solvable'` in `field_theory/abel_ruffini` :
  an irreducible polynomial with an `is_solvable_by_rad` root has solvable Galois group
 * `gal_action_hom_bijective_of_prime_degree'` in `field_theory/polynomial_galois_group` :
  an irreducible polynomial of prime degree with 1-3 non-real roots has full Galois group
 * `equiv.perm.not_solvable` in `group_theory/solvable` : the symmetric group is not solvable

Then all that remains is the construction of a specific polynomial satisfying the conditions of
`gal_action_hom_bijective_of_prime_degree'`, which is done in this file.

-/

namespace abel_ruffini

open function polynomial polynomial.gal ideal

local attribute [instance] splits_ℚ_ℂ

variables (R : Type*) [comm_ring R] (a b : ℕ)

/-- A quintic polynomial that we will show is irreducible -/
noncomputable def Φ : polynomial R := X ^ 5 - C ↑a * X + C ↑b

variables {R}

@[simp] lemma map_Phi {S : Type*} [comm_ring S] (f : R →+* S) : (Φ R a b).map f = Φ S a b :=
by simp [Φ]

@[simp] lemma coeff_zero_Phi : (Φ R a b).coeff 0 = ↑b :=
by simp [Φ, coeff_X_pow]

@[simp] lemma coeff_five_Phi : (Φ R a b).coeff 5 = 1 :=
by simp [Φ, coeff_X, coeff_C, -C_eq_nat_cast, -map_nat_cast]

variables [nontrivial R]

lemma degree_Phi : (Φ R a b).degree = ↑5 :=
begin
  suffices : degree (X ^ 5 - C ↑a * X) = ↑5,
  { rwa [Φ, degree_add_eq_left_of_degree_lt],
    convert degree_C_le.trans_lt (with_bot.coe_lt_coe.mpr (nat.zero_lt_bit1 2)) },
  rw degree_sub_eq_left_of_degree_lt; rw degree_X_pow,
  exact (degree_C_mul_X_le _).trans_lt (with_bot.coe_lt_coe.mpr (nat.one_lt_bit1 two_ne_zero)),
end

lemma nat_degree_Phi : (Φ R a b).nat_degree = 5 :=
nat_degree_eq_of_degree_eq_some (degree_Phi a b)

lemma leading_coeff_Phi : (Φ R a b).leading_coeff = 1 :=
by rw [polynomial.leading_coeff, nat_degree_Phi, coeff_five_Phi]

lemma monic_Phi : (Φ R a b).monic :=
leading_coeff_Phi a b

lemma irreducible_Phi (p : ℕ) (hp : p.prime) (hpa : p ∣ a) (hpb : p ∣ b) (hp2b : ¬ p ^ 2 ∣ b) :
  irreducible (Φ ℚ a b) :=
begin
  rw [←map_Phi a b (int.cast_ring_hom ℚ), ←is_primitive.int.irreducible_iff_irreducible_map_cast],
  apply irreducible_of_eisenstein_criterion,
  { rwa [span_singleton_prime (int.coe_nat_ne_zero.mpr hp.ne_zero), int.prime_iff_nat_abs_prime] },
  { rw [leading_coeff_Phi, mem_span_singleton],
    exact_mod_cast mt nat.dvd_one.mp (hp.ne_one) },
  { intros n hn,
    rw mem_span_singleton,
    rw [degree_Phi, with_bot.coe_lt_coe] at hn,
    interval_cases n with hn;
    simp only [Φ, coeff_X_pow, coeff_C, int.coe_nat_dvd.mpr, hpb, if_true, coeff_C_mul, if_false,
      nat.zero_ne_bit1, eq_self_iff_true, coeff_X_zero, hpa, coeff_add, zero_add, mul_zero,
      int.nat_cast_eq_coe_nat, coeff_sub, sub_self, nat.one_ne_zero, add_zero, coeff_X_one, mul_one,
      zero_sub, dvd_neg, nat.one_eq_bit1, bit0_eq_zero, neg_zero, nat.bit0_ne_bit1,
      dvd_mul_of_dvd_left, nat.bit1_eq_bit1, nat.one_ne_bit0, nat.bit1_ne_zero], },
  { simp only [degree_Phi, ←with_bot.coe_zero, with_bot.coe_lt_coe, nat.succ_pos'] },
  { rw [coeff_zero_Phi, span_singleton_pow, mem_span_singleton, int.nat_cast_eq_coe_nat],
    exact mt int.coe_nat_dvd.mp hp2b },
  all_goals { exact monic.is_primitive (monic_Phi a b) },
end

lemma real_roots_Phi_le : fintype.card ((Φ ℚ a b).root_set ℝ) ≤ 3 :=
begin
  rw [←map_Phi a b (algebra_map ℤ ℚ), Φ, ←one_mul (X ^ 5), ←C_1],
  refine (card_root_set_le_derivative _).trans
    (nat.succ_le_succ ((card_root_set_le_derivative _).trans (nat.succ_le_succ _))),
  suffices : ((C ((algebra_map ℤ ℚ) 20) * X ^ 3).root_set ℝ).subsingleton,
  { norm_num [fintype.card_le_one_iff_subsingleton, ← mul_assoc, *] at * },
  rw root_set_C_mul_X_pow; norm_num,
end

lemma real_roots_Phi_ge_aux (hab : b < a) :
  ∃ x y : ℝ, x ≠ y ∧ aeval x (Φ ℚ a b) = 0 ∧ aeval y (Φ ℚ a b) = 0 :=
begin
  let f := λ x : ℝ, aeval x (Φ ℚ a b),
  have hf : f = λ x, x ^ 5 - a * x + b := by simp [f, Φ],
  have hc : ∀ s : set ℝ, continuous_on f s := λ s, (Φ ℚ a b).continuous_on_aeval,
  have ha : (1 : ℝ) ≤ a := nat.one_le_cast.mpr (nat.one_le_of_lt hab),
  have hle : (0 : ℝ) ≤ 1 := zero_le_one,
  have hf0 : 0 ≤ f 0 := by norm_num [hf],
  by_cases hb : (1 : ℝ) - a + b < 0,
  { have hf1 : f 1 < 0 := by norm_num [hf, hb],
    have hfa : 0 ≤ f a,
    { simp_rw [hf, ←sq],
      refine add_nonneg (sub_nonneg.mpr (pow_le_pow ha _)) _; norm_num },
    obtain ⟨x, ⟨-, hx1⟩, hx2⟩ := intermediate_value_Ico' hle (hc _) (set.mem_Ioc.mpr ⟨hf1, hf0⟩),
    obtain ⟨y, ⟨hy1, -⟩, hy2⟩ := intermediate_value_Ioc ha (hc _) (set.mem_Ioc.mpr ⟨hf1, hfa⟩),
    exact ⟨x, y, (hx1.trans hy1).ne, hx2, hy2⟩ },
  { replace hb : (b : ℝ) = a - 1 := by linarith [show (b : ℝ) + 1 ≤ a, by exact_mod_cast hab],
    have hf1 : f 1 = 0 := by norm_num [hf, hb],
    have hfa := calc f (-a) = a ^ 2 - a ^ 5 + b       : by norm_num [hf, ← sq]
                        ... ≤ a ^ 2 - a ^ 3 + (a - 1) : by refine add_le_add (sub_le_sub_left
                                                            (pow_le_pow ha _) _) _; linarith
                        ... = -(a - 1) ^ 2 * (a + 1)  : by ring
                        ... ≤ 0                       : by nlinarith,
    have ha' := neg_nonpos.mpr (hle.trans ha),
    obtain ⟨x, ⟨-, hx1⟩, hx2⟩ := intermediate_value_Icc ha' (hc _) (set.mem_Icc.mpr ⟨hfa, hf0⟩),
    exact ⟨x, 1, (hx1.trans_lt zero_lt_one).ne, hx2, hf1⟩ },
end

lemma real_roots_Phi_ge (hab : b < a) : 2 ≤ fintype.card ((Φ ℚ a b).root_set ℝ) :=
begin
  have q_ne_zero : Φ ℚ a b ≠ 0 := (monic_Phi a b).ne_zero,
  obtain ⟨x, y, hxy, hx, hy⟩ := real_roots_Phi_ge_aux a b hab,
  have key : ↑({x, y} : finset ℝ) ⊆ (Φ ℚ a b).root_set ℝ,
  { simp [set.insert_subset, mem_root_set q_ne_zero, hx, hy] },
  convert fintype.card_le_of_embedding (set.embedding_of_subset _ _ key),
  simp only [finset.coe_sort_coe, fintype.card_coe, finset.card_singleton,
             finset.card_insert_of_not_mem (mt finset.mem_singleton.mp hxy)]
end

lemma complex_roots_Phi (h : (Φ ℚ a b).separable) : fintype.card ((Φ ℚ a b).root_set ℂ) = 5 :=
(card_root_set_eq_nat_degree h (is_alg_closed.splits_codomain _)).trans (nat_degree_Phi a b)

lemma gal_Phi (hab : b < a) (h_irred : irreducible (Φ ℚ a b)) :
  bijective (gal_action_hom (Φ ℚ a b) ℂ) :=
begin
  apply gal_action_hom_bijective_of_prime_degree' h_irred,
  { norm_num [nat_degree_Phi] },
  { rw [complex_roots_Phi a b h_irred.separable, nat.succ_le_succ_iff],
    exact (real_roots_Phi_le a b).trans (nat.le_succ 3) },
  { simp_rw [complex_roots_Phi a b h_irred.separable, nat.succ_le_succ_iff],
    exact real_roots_Phi_ge a b hab },
end

theorem not_solvable_by_rad (p : ℕ) (x : ℂ) (hx : aeval x (Φ ℚ a b) = 0) (hab : b < a)
  (hp : p.prime) (hpa : p ∣ a) (hpb : p ∣ b) (hp2b : ¬ p ^ 2 ∣ b) :
  ¬ is_solvable_by_rad ℚ x :=
begin
  have h_irred := irreducible_Phi a b p hp hpa hpb hp2b,
  apply mt (solvable_by_rad.is_solvable' h_irred hx),
  introI h,
  refine equiv.perm.not_solvable _ (le_of_eq _)
    (solvable_of_surjective (gal_Phi a b hab h_irred).2),
  rw_mod_cast [cardinal.mk_fintype, complex_roots_Phi a b h_irred.separable],
end

theorem not_solvable_by_rad' (x : ℂ) (hx : aeval x (Φ ℚ 4 2) = 0) :
  ¬ is_solvable_by_rad ℚ x :=
by apply not_solvable_by_rad 4 2 2 x hx; norm_num

/-- **Abel-Ruffini Theorem** -/
theorem exists_not_solvable_by_rad : ∃ x : ℂ, is_algebraic ℚ x ∧ ¬ is_solvable_by_rad ℚ x :=
begin
  obtain ⟨x, hx⟩ := exists_root_of_splits (algebra_map ℚ ℂ)
    (is_alg_closed.splits_codomain (Φ ℚ 4 2))
    (ne_of_eq_of_ne (degree_Phi 4 2) (mt with_bot.coe_eq_coe.mp (nat.bit1_ne_zero 2))),
  exact ⟨x, ⟨Φ ℚ 4 2, (monic_Phi 4 2).ne_zero, hx⟩, not_solvable_by_rad' x hx⟩,
end

end abel_ruffini


/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kunhao Zheng, Stanislas Polu, David Renshaw, OpenAI GPT-f
-/
import mathzoo.imports.miniF2F

open_locale nat rat real big_operators topological_space

theorem mathd_algebra_59
  (b : ℝ)
  (h₀ : (4:ℝ)^b + 2^3 = 12) :
  b = 1 :=
begin
  have h₁ : (4:ℝ)^b = 4, linarith,
  by_contradiction h,
  clear h₀,
  change b ≠ 1 at h,
  by_cases b₀ : b < 1,
  have key₁ : (4:ℝ)^b < (4:ℝ)^(1:ℝ), {
    apply real.rpow_lt_rpow_of_exponent_lt _ _,
    linarith,
    exact b₀,
  },
  simp at key₁,
  have key₂ : (4:ℝ)^b ≠ (4:ℝ), {
    exact ne_of_lt key₁,
  },
  exact h (false.rec (b = 1) (key₂ h₁)),
  have key₃ : 1 < b, {
    refine h.symm.le_iff_lt.mp _,
    exact not_lt.mp b₀,
  },
  have key₄ : (4:ℝ)^(1:ℝ) < (4:ℝ)^b, {
    apply real.rpow_lt_rpow_of_exponent_lt _ _,
    linarith,
    exact key₃,
  },
  simp at key₄,
  have key₂ : (4:ℝ)^b ≠ (4:ℝ), {
    rw ne_comm,
    exact ne_of_lt key₄,
  },
  exact h (false.rec (b = 1) (key₂ h₁)),
end


-- Simple test.

import data.real.basic

section real_tree

inductive real_tree
| Leaf : ℝ → real_tree
| Node : real_tree → real_tree → real_tree

open real_tree

--#print real_tree.rec
--#print real_tree.cases_on

def sum_real_tree : real_tree → ℝ
| (Leaf r) := r
| (Node t1 t2) := (sum_real_tree t1) + (sum_real_tree t2)

end real_tree

-- List construction (Nested inductive).
-- Note: This will change in Lean 4?

section real_branching_tree

inductive real_branching_tree
| Leaf : ℝ → real_branching_tree
| Node : list real_branching_tree → real_branching_tree

open real_branching_tree

--#print real_branching_tree.rec
--#print real_branching_tree.cases_on

@[elab_as_eliminator]
protected def real_branching_tree.cases_on'
: Π {C : real_branching_tree → Sort*} (x : real_branching_tree),
  (Π (a : ℝ), C (Leaf a)) →
  (Π (l : list real_branching_tree), C (Node l)) →
  C x
| C (Leaf a) mL mN := mL a
| C (Node l) mL mN := mN l

def real_branching_tree.lt : real_branching_tree → real_branching_tree → Prop
| t (Node l) := t ∈ l
| t _ := false

instance : has_lt real_branching_tree := ⟨real_branching_tree.lt⟩

@[simp] lemma real_branching_tree.lt_Node
(t : real_branching_tree) (l : list real_branching_tree)
: t < (Node l) ↔ t ∈ l :=
by rw [←real_branching_tree.lt.equations._eqn_2 t l]; refl

lemma real_branching_tree.lt_sizeof (t1 t2 : real_branching_tree)
: t1 < t2 → sizeof t1 < sizeof t2 :=
begin
    intro H,
    cases t2 with r l,
    { cases H, },
    { change sizeof t1 < real_branching_tree.sizeof (Node l),
      rw real_branching_tree.Node.sizeof_spec l,
      have H1 := (real_branching_tree.lt_Node t1 l).1 H,
      have H2 := list.sizeof_lt_sizeof_of_mem H1,
      exact ((nat.one_add (sizeof l)).symm ▸ (nat.lt_succ_of_lt H2)), }
end

lemma real_branching_tree.lt_well_founded : well_founded real_branching_tree.lt :=
(subrelation.wf real_branching_tree.lt_sizeof) (inv_image.wf _ nat.lt_wf)

@[elab_as_eliminator]
protected def real_branching_tree.rec'
: Π {C : real_branching_tree → Sort*},
  (Π (a : ℝ), C (Leaf a)) →
  (Π (a : list real_branching_tree), C (Node a)) →
  Π (x : real_branching_tree), C x :=
begin
    intros C m1 m2 x,
    apply (well_founded.fix real_branching_tree.lt_well_founded),
    intros y Hy,
    exact (real_branching_tree.cases_on' y m1 m2),
end

--def real_branching_tree.sum : real_branching_tree → ℝ
--| (Leaf r) := r
--| (Node l) := list.sum (list.map real_branching_tree.sum l)

-- This is still not good enough. Best we can do:

-- mutual inductive foo, list_foo
-- with foo : Type
-- | mk : list_foo -> foo
-- with list_foo : Type
-- | nil : list_foo
-- | cons : foo -> list_foo -> list_foo

end real_branching_tree


/-
Copyright (c) 2022 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov

! This file was ported from Lean 3 source module measure_theory.measure.ae_disjoint
! leanprover-community/mathlib commit bc7d81beddb3d6c66f71449c5bc76c38cb77cf9e
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.MeasureTheory.Measure.MeasureSpaceDef

/-!
# Almost everywhere disjoint sets

We say that sets `s` and `t` are `μ`-a.e. disjoint (see `measure_theory.ae_disjoint`) if their
intersection has measure zero. This assumption can be used instead of `disjoint` in most theorems in
measure theory.
-/

open Set Function

namespace MeasureTheory

variable {ι α : Type _} {m : MeasurableSpace α} (μ : Measure α)

/-- Two sets are said to be `μ`-a.e. disjoint if their intersection has measure zero. -/
def AeDisjoint (s t : Set α) :=
  μ (s ∩ t) = 0
#align measure_theory.ae_disjoint MeasureTheory.AeDisjoint

variable {μ} {s t u v : Set α}

/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (j «expr ≠ » i) -/
/-- If `s : ι → set α` is a countable family of pairwise a.e. disjoint sets, then there exists a
family of measurable null sets `t i` such that `s i \ t i` are pairwise disjoint. -/
theorem exists_null_pairwise_disjoint_diff [Countable ι] {s : ι → Set α}
    (hd : Pairwise (AeDisjoint μ on s)) :
    ∃ t : ι → Set α,
      (∀ i, MeasurableSet (t i)) ∧ (∀ i, μ (t i) = 0) ∧ Pairwise (Disjoint on fun i => s i \ t i) :=
  by
  refine'
    ⟨fun i => to_measurable μ (s i ∩ ⋃ j ∈ ({i}ᶜ : Set ι), s j), fun i =>
      measurable_set_to_measurable _ _, fun i => _, _⟩
  · simp only [measure_to_measurable, inter_Union]
    exact (measure_bUnion_null_iff <| to_countable _).2 fun j hj => hd (Ne.symm hj)
  · simp only [Pairwise, disjoint_left, on_fun, mem_diff, not_and, and_imp, Classical.not_not]
    intro i j hne x hi hU hj
    replace hU : x ∉ s i ∩ ⋃ (j) (_ : j ≠ i), s j := fun h => hU (subset_to_measurable _ _ h)
    simp only [mem_inter_iff, mem_Union, not_and, not_exists] at hU
    exact (hU hi j hne.symm hj).elim
#align measure_theory.exists_null_pairwise_disjoint_diff MeasureTheory.exists_null_pairwise_disjoint_diff

namespace AeDisjoint

protected theorem eq (h : AeDisjoint μ s t) : μ (s ∩ t) = 0 :=
  h
#align measure_theory.ae_disjoint.eq MeasureTheory.AeDisjoint.eq

@[symm]
protected theorem symm (h : AeDisjoint μ s t) : AeDisjoint μ t s := by rwa [ae_disjoint, inter_comm]
#align measure_theory.ae_disjoint.symm MeasureTheory.AeDisjoint.symm

protected theorem symmetric : Symmetric (AeDisjoint μ) := fun s t h => h.symm
#align measure_theory.ae_disjoint.symmetric MeasureTheory.AeDisjoint.symmetric

protected theorem comm : AeDisjoint μ s t ↔ AeDisjoint μ t s :=
  ⟨fun h => h.symm, fun h => h.symm⟩
#align measure_theory.ae_disjoint.comm MeasureTheory.AeDisjoint.comm

protected theorem Disjoint.aeDisjoint (h : Disjoint s t) : AeDisjoint μ s t := by
  rw [ae_disjoint, disjoint_iff_inter_eq_empty.1 h, measure_empty]
#align disjoint.ae_disjoint Disjoint.aeDisjoint

protected theorem Pairwise.aeDisjoint {f : ι → Set α} (hf : Pairwise (Disjoint on f)) :
    Pairwise (AeDisjoint μ on f) :=
  hf.mono fun i j h => h.AeDisjoint
#align pairwise.ae_disjoint Pairwise.aeDisjoint

protected theorem Set.PairwiseDisjoint.aeDisjoint {f : ι → Set α} {s : Set ι}
    (hf : s.PairwiseDisjoint f) : s.Pairwise (AeDisjoint μ on f) :=
  hf.mono' fun i j h => h.AeDisjoint
#align set.pairwise_disjoint.ae_disjoint Set.PairwiseDisjoint.aeDisjoint

theorem monoAe (h : AeDisjoint μ s t) (hu : u ≤ᵐ[μ] s) (hv : v ≤ᵐ[μ] t) : AeDisjoint μ u v :=
  measure_mono_null_ae (hu.inter hv) h
#align measure_theory.ae_disjoint.mono_ae MeasureTheory.AeDisjoint.monoAe

protected theorem mono (h : AeDisjoint μ s t) (hu : u ⊆ s) (hv : v ⊆ t) : AeDisjoint μ u v :=
  h.monoAe hu.EventuallyLE hv.EventuallyLE
#align measure_theory.ae_disjoint.mono MeasureTheory.AeDisjoint.mono

protected theorem congr (h : AeDisjoint μ s t) (hu : u =ᵐ[μ] s) (hv : v =ᵐ[μ] t) :
    AeDisjoint μ u v :=
  h.monoAe (Filter.EventuallyEq.le hu) (Filter.EventuallyEq.le hv)
#align measure_theory.ae_disjoint.congr MeasureTheory.AeDisjoint.congr

@[simp]
theorem unionᵢ_left_iff [Countable ι] {s : ι → Set α} :
    AeDisjoint μ (⋃ i, s i) t ↔ ∀ i, AeDisjoint μ (s i) t := by
  simp only [ae_disjoint, Union_inter, measure_Union_null_iff]
#align measure_theory.ae_disjoint.Union_left_iff MeasureTheory.AeDisjoint.unionᵢ_left_iff

@[simp]
theorem unionᵢ_right_iff [Countable ι] {t : ι → Set α} :
    AeDisjoint μ s (⋃ i, t i) ↔ ∀ i, AeDisjoint μ s (t i) := by
  simp only [ae_disjoint, inter_Union, measure_Union_null_iff]
#align measure_theory.ae_disjoint.Union_right_iff MeasureTheory.AeDisjoint.unionᵢ_right_iff

@[simp]
theorem union_left_iff : AeDisjoint μ (s ∪ t) u ↔ AeDisjoint μ s u ∧ AeDisjoint μ t u := by
  simp [union_eq_Union, and_comm]
#align measure_theory.ae_disjoint.union_left_iff MeasureTheory.AeDisjoint.union_left_iff

@[simp]
theorem union_right_iff : AeDisjoint μ s (t ∪ u) ↔ AeDisjoint μ s t ∧ AeDisjoint μ s u := by
  simp [union_eq_Union, and_comm]
#align measure_theory.ae_disjoint.union_right_iff MeasureTheory.AeDisjoint.union_right_iff

theorem unionLeft (hs : AeDisjoint μ s u) (ht : AeDisjoint μ t u) : AeDisjoint μ (s ∪ t) u :=
  union_left_iff.mpr ⟨hs, ht⟩
#align measure_theory.ae_disjoint.union_left MeasureTheory.AeDisjoint.unionLeft

theorem unionRight (ht : AeDisjoint μ s t) (hu : AeDisjoint μ s u) : AeDisjoint μ s (t ∪ u) :=
  union_right_iff.2 ⟨ht, hu⟩
#align measure_theory.ae_disjoint.union_right MeasureTheory.AeDisjoint.unionRight

theorem diff_ae_eq_left (h : AeDisjoint μ s t) : (s \ t : Set α) =ᵐ[μ] s :=
  @diff_self_inter _ s t ▸ diff_null_ae_eq_self h
#align measure_theory.ae_disjoint.diff_ae_eq_left MeasureTheory.AeDisjoint.diff_ae_eq_left

theorem diff_ae_eq_right (h : AeDisjoint μ s t) : (t \ s : Set α) =ᵐ[μ] t :=
  h.symm.diff_ae_eq_left
#align measure_theory.ae_disjoint.diff_ae_eq_right MeasureTheory.AeDisjoint.diff_ae_eq_right

theorem measure_diff_left (h : AeDisjoint μ s t) : μ (s \ t) = μ s :=
  measure_congr h.diff_ae_eq_left
#align measure_theory.ae_disjoint.measure_diff_left MeasureTheory.AeDisjoint.measure_diff_left

theorem measure_diff_right (h : AeDisjoint μ s t) : μ (t \ s) = μ t :=
  measure_congr h.diff_ae_eq_right
#align measure_theory.ae_disjoint.measure_diff_right MeasureTheory.AeDisjoint.measure_diff_right

/-- If `s` and `t` are `μ`-a.e. disjoint, then `s \ u` and `t` are disjoint for some measurable null
set `u`. -/
theorem exists_disjoint_diff (h : AeDisjoint μ s t) :
    ∃ u, MeasurableSet u ∧ μ u = 0 ∧ Disjoint (s \ u) t :=
  ⟨toMeasurable μ (s ∩ t), measurableSet_toMeasurable _ _, (measure_toMeasurable _).trans h,
    disjoint_sdiff_self_left.mono_left fun x hx =>
      ⟨hx.1, fun hxt => hx.2 <| subset_toMeasurable _ _ ⟨hx.1, hxt⟩⟩⟩
#align measure_theory.ae_disjoint.exists_disjoint_diff MeasureTheory.AeDisjoint.exists_disjoint_diff

theorem ofNullRight (h : μ t = 0) : AeDisjoint μ s t :=
  measure_mono_null (inter_subset_right _ _) h
#align measure_theory.ae_disjoint.of_null_right MeasureTheory.AeDisjoint.ofNullRight

theorem ofNullLeft (h : μ s = 0) : AeDisjoint μ s t :=
  (ofNullRight h).symm
#align measure_theory.ae_disjoint.of_null_left MeasureTheory.AeDisjoint.ofNullLeft

end AeDisjoint

theorem aeDisjointComplLeft : AeDisjoint μ (sᶜ) s :=
  (@disjoint_compl_left _ _ s).AeDisjoint
#align measure_theory.ae_disjoint_compl_left MeasureTheory.aeDisjointComplLeft

theorem aeDisjointComplRight : AeDisjoint μ s (sᶜ) :=
  (@disjoint_compl_right _ _ s).AeDisjoint
#align measure_theory.ae_disjoint_compl_right MeasureTheory.aeDisjointComplRight

end MeasureTheory


/-
Copyright (c) 2021 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import topology.instances.real
import order.filter.archimedean

/-!
# Convergence of subadditive sequences

A subadditive sequence `u : ℕ → ℝ` is a sequence satisfying `u (m + n) ≤ u m + u n` for all `m, n`.
We define this notion as `subadditive u`, and prove in `subadditive.tendsto_lim` that, if `u n / n`
is bounded below, then it converges to a limit (that we denote by `subadditive.lim` for
convenience). This result is known as Fekete's lemma in the literature.
-/

noncomputable theory
open set filter
open_locale topological_space

/-- A real-valued sequence is subadditive if it satisfies the inequality `u (m + n) ≤ u m + u n`
for all `m, n`. -/
def subadditive (u : ℕ → ℝ) : Prop :=
∀ m n, u (m + n) ≤ u m + u n

namespace subadditive

variables {u : ℕ → ℝ} (h : subadditive u)
include h

/-- The limit of a bounded-below subadditive sequence. The fact that the sequence indeed tends to
this limit is given in `subadditive.tendsto_lim` -/
@[irreducible, nolint unused_arguments]
protected def lim := Inf ((λ (n : ℕ), u n / n) '' (Ici 1))

lemma lim_le_div (hbdd : bdd_below (range (λ n, u n / n))) {n : ℕ} (hn : n ≠ 0) :
  h.lim ≤ u n / n :=
begin
  rw subadditive.lim,
  apply cInf_le _ _,
  { rcases hbdd with ⟨c, hc⟩,
    exact ⟨c, λ x hx, hc (image_subset_range _ _ hx)⟩ },
  { apply mem_image_of_mem,
    exact zero_lt_iff.2 hn }
end

lemma apply_mul_add_le (k n r) : u (k * n + r) ≤ k * u n + u r :=
begin
  induction k with k IH, { simp only [nat.cast_zero, zero_mul, zero_add] },
  calc
  u ((k+1) * n + r)
      = u (n + (k * n + r)) : by { congr' 1, ring }
  ... ≤ u n + u (k * n + r) : h _ _
  ... ≤ u n + (k * u n + u r) : add_le_add_left IH _
  ... = (k+1 : ℕ) * u n + u r : by simp; ring
end

lemma eventually_div_lt_of_div_lt {L : ℝ} {n : ℕ} (hn : n ≠ 0) (hL : u n / n < L) :
  ∀ᶠ p in at_top, u p / p < L :=
begin
  have I : ∀ (i : ℕ), 0 < i → (i : ℝ) ≠ 0,
  { assume i hi, simp only [hi.ne', ne.def, nat.cast_eq_zero, not_false_iff] },
  obtain ⟨w, nw, wL⟩ : ∃ w, u n / n < w ∧ w < L := exists_between hL,
  obtain ⟨x, hx⟩ : ∃ x, ∀ i < n, u i - i * w ≤ x,
  { obtain ⟨x, hx⟩ : bdd_above (↑(finset.image (λ i, u i - i * w) (finset.range n))) :=
      finset.bdd_above _,
    refine ⟨x, λ i hi, _⟩,
    simp only [upper_bounds, mem_image, and_imp, forall_exists_index, mem_set_of_eq,
      forall_apply_eq_imp_iff₂, finset.mem_range, finset.mem_coe, finset.coe_image] at hx,
    exact hx _ hi },
  have A : ∀ (p : ℕ), u p ≤ p * w + x,
  { assume p,
    let s := p / n,
    let r := p % n,
    have hp : p = s * n + r, by rw [mul_comm, nat.div_add_mod],
    calc u p = u (s * n + r) : by rw hp
    ... ≤ s * u n + u r : h.apply_mul_add_le _ _ _
    ... = s * n * (u n / n) + u r : by { field_simp [I _ hn.bot_lt], ring }
    ... ≤ s * n * w + u r : add_le_add_right
      (mul_le_mul_of_nonneg_left nw.le (mul_nonneg (nat.cast_nonneg _) (nat.cast_nonneg _))) _
    ... = (s * n + r) * w + (u r - r * w) : by ring
    ... = p * w + (u r - r * w) : by { rw hp, simp only [nat.cast_add, nat.cast_mul] }
    ... ≤ p * w + x : add_le_add_left (hx _ (nat.mod_lt _ hn.bot_lt)) _ },
  have B : ∀ᶠ p in at_top, u p / p ≤ w + x / p,
  { refine eventually_at_top.2 ⟨1, λ p hp, _⟩,
    simp only [I p hp, ne.def, not_false_iff] with field_simps,
    refine div_le_div_of_le_of_nonneg _ (nat.cast_nonneg _),
    rw mul_comm,
    exact A _ },
  have C : ∀ᶠ (p : ℕ) in at_top, w + x / p < L,
  { have : tendsto (λ (p : ℕ), w + x / p) at_top (𝓝 (w + 0)) :=
      tendsto_const_nhds.add (tendsto_const_nhds.div_at_top tendsto_coe_nat_at_top_at_top),
    rw add_zero at this,
    exact (tendsto_order.1 this).2 _ wL },
  filter_upwards [B, C] with _ hp h'p using hp.trans_lt h'p,
end

/-- Fekete's lemma: a subadditive sequence which is bounded below converges. -/
theorem tendsto_lim (hbdd : bdd_below (range (λ n, u n / n))) :
  tendsto (λ n, u n / n) at_top (𝓝 h.lim) :=
begin
  refine tendsto_order.2 ⟨λ l hl, _, λ L hL, _⟩,
  { refine eventually_at_top.2
      ⟨1, λ n hn, hl.trans_le (h.lim_le_div hbdd ((zero_lt_one.trans_le hn).ne'))⟩ },
  { obtain ⟨n, npos, hn⟩ : ∃ (n : ℕ), 0 < n ∧ u n / n < L,
    { rw subadditive.lim at hL,
      rcases exists_lt_of_cInf_lt (by simp) hL with ⟨x, hx, xL⟩,
      rcases (mem_image _ _ _).1 hx with ⟨n, hn, rfl⟩,
      exact ⟨n, zero_lt_one.trans_le hn, xL⟩ },
    exact h.eventually_div_lt_of_div_lt npos.ne' hn }
end

end subadditive


/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kunhao Zheng, Stanislas Polu, David Renshaw, OpenAI GPT-f
-/
import mathzoo.imports.miniF2F

open_locale nat rat real big_operators topological_space

theorem mathd_algebra_342
  (a d: ℝ)
  (h₀ : ∑ k in (finset.range 5), (a + k * d) = 70)
  (h₁ : ∑ k in (finset.range 10), (a + k * d) = 210) :
  a = 42/5 :=
begin
  revert h₀ h₁,
  simp [finset.sum_range_succ, mul_comm d],
  intros,
  linarith,
end