/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne, Benjamin Davidson

! This file was ported from Lean 3 source module analysis.special_functions.complex.log
! 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.Analysis.SpecialFunctions.Complex.Arg
import Mathbin.Analysis.SpecialFunctions.Log.Basic

/-!
# The complex `log` function

Basic properties, relationship with `exp`.
-/

noncomputable section

namespace Complex

open Set Filter

open Real Topology ComplexConjugate

/-- Inverse of the `exp` function. Returns values such that `(log x).im > - π` and `(log x).im ≤ π`.
  `log 0 = 0`-/
@[pp_nodot]
noncomputable def log (x : ℂ) : ℂ :=
  x.abs.log + arg x * I
#align complex.log Complex.log

theorem log_re (x : ℂ) : x.log.re = x.abs.log := by simp [log]
#align complex.log_re Complex.log_re

theorem log_im (x : ℂ) : x.log.im = x.arg := by simp [log]
#align complex.log_im Complex.log_im

theorem neg_pi_lt_log_im (x : ℂ) : -π < (log x).im := by simp only [log_im, neg_pi_lt_arg]
#align complex.neg_pi_lt_log_im Complex.neg_pi_lt_log_im

theorem log_im_le_pi (x : ℂ) : (log x).im ≤ π := by simp only [log_im, arg_le_pi]
#align complex.log_im_le_pi Complex.log_im_le_pi

theorem exp_log {x : ℂ} (hx : x ≠ 0) : exp (log x) = x := by
  rw [log, exp_add_mul_I, ← of_real_sin, sin_arg, ← of_real_cos, cos_arg hx, ← of_real_exp,
    Real.exp_log (abs.pos hx), mul_add, of_real_div, of_real_div,
    mul_div_cancel' _ (of_real_ne_zero.2 <| abs.ne_zero hx), ← mul_assoc,
    mul_div_cancel' _ (of_real_ne_zero.2 <| abs.ne_zero hx), re_add_im]
#align complex.exp_log Complex.exp_log

@[simp]
theorem range_exp : range exp = {0}ᶜ :=
  Set.ext fun x =>
    ⟨by
      rintro ⟨x, rfl⟩
      exact exp_ne_zero x, fun hx => ⟨log x, exp_log hx⟩⟩
#align complex.range_exp Complex.range_exp

theorem log_exp {x : ℂ} (hx₁ : -π < x.im) (hx₂ : x.im ≤ π) : log (exp x) = x := by
  rw [log, abs_exp, Real.log_exp, exp_eq_exp_re_mul_sin_add_cos, ← of_real_exp,
    arg_mul_cos_add_sin_mul_I (Real.exp_pos _) ⟨hx₁, hx₂⟩, re_add_im]
#align complex.log_exp Complex.log_exp

theorem exp_inj_of_neg_pi_lt_of_le_pi {x y : ℂ} (hx₁ : -π < x.im) (hx₂ : x.im ≤ π) (hy₁ : -π < y.im)
    (hy₂ : y.im ≤ π) (hxy : exp x = exp y) : x = y := by
  rw [← log_exp hx₁ hx₂, ← log_exp hy₁ hy₂, hxy]
#align complex.exp_inj_of_neg_pi_lt_of_le_pi Complex.exp_inj_of_neg_pi_lt_of_le_pi

theorem of_real_log {x : ℝ} (hx : 0 ≤ x) : (x.log : ℂ) = log x :=
  Complex.ext (by rw [log_re, of_real_re, abs_of_nonneg hx])
    (by rw [of_real_im, log_im, arg_of_real_of_nonneg hx])
#align complex.of_real_log Complex.of_real_log

theorem log_of_real_re (x : ℝ) : (log (x : ℂ)).re = Real.log x := by simp [log_re]
#align complex.log_of_real_re Complex.log_of_real_re

theorem log_of_real_mul {r : ℝ} (hr : 0 < r) {x : ℂ} (hx : x ≠ 0) :
    log (r * x) = Real.log r + log x :=
  by
  replace hx := complex.abs.ne_zero_iff.mpr hx
  simp_rw [log, map_mul, abs_of_real, arg_real_mul _ hr, abs_of_pos hr, Real.log_mul hr.ne' hx,
    of_real_add, add_assoc]
#align complex.log_of_real_mul Complex.log_of_real_mul

theorem log_mul_of_real (r : ℝ) (hr : 0 < r) (x : ℂ) (hx : x ≠ 0) :
    log (x * r) = Real.log r + log x := by rw [mul_comm, log_of_real_mul hr hx, add_comm]
#align complex.log_mul_of_real Complex.log_mul_of_real

@[simp]
theorem log_zero : log 0 = 0 := by simp [log]
#align complex.log_zero Complex.log_zero

@[simp]
theorem log_one : log 1 = 0 := by simp [log]
#align complex.log_one Complex.log_one

theorem log_neg_one : log (-1) = π * I := by simp [log]
#align complex.log_neg_one Complex.log_neg_one

theorem log_i : log I = π / 2 * I := by simp [log]
#align complex.log_I Complex.log_i

theorem log_neg_i : log (-I) = -(π / 2) * I := by simp [log]
#align complex.log_neg_I Complex.log_neg_i

theorem log_conj_eq_ite (x : ℂ) : log (conj x) = if x.arg = π then log x else conj (log x) :=
  by
  simp_rw [log, abs_conj, arg_conj, map_add, map_mul, conj_of_real]
  split_ifs with hx
  · rw [hx]
  simp_rw [of_real_neg, conj_I, mul_neg, neg_mul]
#align complex.log_conj_eq_ite Complex.log_conj_eq_ite

theorem log_conj (x : ℂ) (h : x.arg ≠ π) : log (conj x) = conj (log x) := by
  rw [log_conj_eq_ite, if_neg h]
#align complex.log_conj Complex.log_conj

theorem log_inv_eq_ite (x : ℂ) : log x⁻¹ = if x.arg = π then -conj (log x) else -log x :=
  by
  by_cases hx : x = 0
  · simp [hx]
  rw [inv_def, log_mul_of_real, Real.log_inv, of_real_neg, ← sub_eq_neg_add, log_conj_eq_ite]
  · simp_rw [log, map_add, map_mul, conj_of_real, conj_I, norm_sq_eq_abs, Real.log_pow,
      Nat.cast_two, of_real_mul, of_real_bit0, of_real_one, neg_add, mul_neg, two_mul, neg_neg]
    split_ifs
    · rw [add_sub_right_comm, sub_add_cancel']
    · rw [add_sub_right_comm, sub_add_cancel']
  · rwa [inv_pos, Complex.normSq_pos]
  · rwa [map_ne_zero]
#align complex.log_inv_eq_ite Complex.log_inv_eq_ite

theorem log_inv (x : ℂ) (hx : x.arg ≠ π) : log x⁻¹ = -log x := by rw [log_inv_eq_ite, if_neg hx]
#align complex.log_inv Complex.log_inv

theorem two_pi_i_ne_zero : (2 * π * I : ℂ) ≠ 0 := by norm_num [Real.pi_ne_zero, I_ne_zero]
#align complex.two_pi_I_ne_zero Complex.two_pi_i_ne_zero

theorem exp_eq_one_iff {x : ℂ} : exp x = 1 ↔ ∃ n : ℤ, x = n * (2 * π * I) :=
  by
  constructor
  · intro h
    rcases existsUnique_add_zsmul_mem_Ioc Real.two_pi_pos x.im (-π) with ⟨n, hn, -⟩
    use -n
    rw [Int.cast_neg, neg_mul, eq_neg_iff_add_eq_zero]
    have : (x + n * (2 * π * I)).im ∈ Ioc (-π) π := by simpa [two_mul, mul_add] using hn
    rw [← log_exp this.1 this.2, exp_periodic.int_mul n, h, log_one]
  · rintro ⟨n, rfl⟩
    exact (exp_periodic.int_mul n).Eq.trans exp_zero
#align complex.exp_eq_one_iff Complex.exp_eq_one_iff

theorem exp_eq_exp_iff_exp_sub_eq_one {x y : ℂ} : exp x = exp y ↔ exp (x - y) = 1 := by
  rw [exp_sub, div_eq_one_iff_eq (exp_ne_zero _)]
#align complex.exp_eq_exp_iff_exp_sub_eq_one Complex.exp_eq_exp_iff_exp_sub_eq_one

theorem exp_eq_exp_iff_exists_int {x y : ℂ} : exp x = exp y ↔ ∃ n : ℤ, x = y + n * (2 * π * I) := by
  simp only [exp_eq_exp_iff_exp_sub_eq_one, exp_eq_one_iff, sub_eq_iff_eq_add']
#align complex.exp_eq_exp_iff_exists_int Complex.exp_eq_exp_iff_exists_int

@[simp]
theorem countable_preimage_exp {s : Set ℂ} : (exp ⁻¹' s).Countable ↔ s.Countable :=
  by
  refine' ⟨fun hs => _, fun hs => _⟩
  · refine' ((hs.image exp).insert 0).mono _
    rw [image_preimage_eq_inter_range, range_exp, ← diff_eq, ← union_singleton, diff_union_self]
    exact subset_union_left _ _
  · rw [← bUnion_preimage_singleton]
    refine' hs.bUnion fun z hz => _
    rcases em (∃ w, exp w = z) with (⟨w, rfl⟩ | hne)
    · simp only [preimage, mem_singleton_iff, exp_eq_exp_iff_exists_int, set_of_exists]
      exact countable_Union fun m => countable_singleton _
    · push_neg  at hne
      simp [preimage, hne]
#align complex.countable_preimage_exp Complex.countable_preimage_exp

alias countable_preimage_exp ↔ _ _root_.set.countable.preimage_cexp
#align set.countable.preimage_cexp Set.Countable.preimage_cexp

theorem tendsto_log_nhdsWithin_im_neg_of_re_neg_of_im_zero {z : ℂ} (hre : z.re < 0)
    (him : z.im = 0) : Tendsto log (𝓝[{ z : ℂ | z.im < 0 }] z) (𝓝 <| Real.log (abs z) - π * I) :=
  by
  have :=
    (continuous_of_real.continuous_at.comp_continuous_within_at
            (continuous_abs.continuous_within_at.log _)).Tendsto.add
      (((continuous_of_real.tendsto _).comp <|
            tendsto_arg_nhds_within_im_neg_of_re_neg_of_im_zero hre him).mul
        tendsto_const_nhds)
  convert this
  · simp [sub_eq_add_neg]
  · lift z to ℝ using him
    simpa using hre.ne
#align complex.tendsto_log_nhds_within_im_neg_of_re_neg_of_im_zero Complex.tendsto_log_nhdsWithin_im_neg_of_re_neg_of_im_zero

theorem continuousWithinAt_log_of_re_neg_of_im_zero {z : ℂ} (hre : z.re < 0) (him : z.im = 0) :
    ContinuousWithinAt log { z : ℂ | 0 ≤ z.im } z :=
  by
  have :=
    (continuous_of_real.continuous_at.comp_continuous_within_at
            (continuous_abs.continuous_within_at.log _)).Tendsto.add
      ((continuous_of_real.continuous_at.comp_continuous_within_at <|
            continuous_within_at_arg_of_re_neg_of_im_zero hre him).mul
        tendsto_const_nhds)
  convert this
  · lift z to ℝ using him
    simpa using hre.ne
#align complex.continuous_within_at_log_of_re_neg_of_im_zero Complex.continuousWithinAt_log_of_re_neg_of_im_zero

theorem tendsto_log_nhdsWithin_im_nonneg_of_re_neg_of_im_zero {z : ℂ} (hre : z.re < 0)
    (him : z.im = 0) : Tendsto log (𝓝[{ z : ℂ | 0 ≤ z.im }] z) (𝓝 <| Real.log (abs z) + π * I) := by
  simpa only [log, arg_eq_pi_iff.2 ⟨hre, him⟩] using
    (continuous_within_at_log_of_re_neg_of_im_zero hre him).Tendsto
#align complex.tendsto_log_nhds_within_im_nonneg_of_re_neg_of_im_zero Complex.tendsto_log_nhdsWithin_im_nonneg_of_re_neg_of_im_zero

@[simp]
theorem map_exp_comap_re_atBot : map exp (comap re atBot) = 𝓝[≠] 0 := by
  rw [← comap_exp_nhds_zero, map_comap, range_exp, nhdsWithin]
#align complex.map_exp_comap_re_at_bot Complex.map_exp_comap_re_atBot

@[simp]
theorem map_exp_comap_re_atTop : map exp (comap re atTop) = comap abs atTop :=
  by
  rw [← comap_exp_comap_abs_at_top, map_comap, range_exp, inf_eq_left, le_principal_iff]
  exact eventually_ne_of_tendsto_norm_atTop tendsto_comap 0
#align complex.map_exp_comap_re_at_top Complex.map_exp_comap_re_atTop

end Complex

section LogDeriv

open Complex Filter

open Topology

variable {α : Type _}

theorem continuousAt_clog {x : ℂ} (h : 0 < x.re ∨ x.im ≠ 0) : ContinuousAt log x :=
  by
  refine' ContinuousAt.add _ _
  · refine' continuous_of_real.continuous_at.comp _
    refine' (Real.continuousAt_log _).comp complex.continuous_abs.continuous_at
    rw [complex.abs.ne_zero_iff]
    rintro rfl
    simpa using h
  · have h_cont_mul : Continuous fun x : ℂ => x * I := continuous_id'.mul continuous_const
    refine' h_cont_mul.continuous_at.comp (continuous_of_real.continuous_at.comp _)
    exact continuous_at_arg h
#align continuous_at_clog continuousAt_clog

theorem Filter.Tendsto.clog {l : Filter α} {f : α → ℂ} {x : ℂ} (h : Tendsto f l (𝓝 x))
    (hx : 0 < x.re ∨ x.im ≠ 0) : Tendsto (fun t => log (f t)) l (𝓝 <| log x) :=
  (continuousAt_clog hx).Tendsto.comp h
#align filter.tendsto.clog Filter.Tendsto.clog

variable [TopologicalSpace α]

theorem ContinuousAt.clog {f : α → ℂ} {x : α} (h₁ : ContinuousAt f x)
    (h₂ : 0 < (f x).re ∨ (f x).im ≠ 0) : ContinuousAt (fun t => log (f t)) x :=
  h₁.clog h₂
#align continuous_at.clog ContinuousAt.clog

theorem ContinuousWithinAt.clog {f : α → ℂ} {s : Set α} {x : α} (h₁ : ContinuousWithinAt f s x)
    (h₂ : 0 < (f x).re ∨ (f x).im ≠ 0) : ContinuousWithinAt (fun t => log (f t)) s x :=
  h₁.clog h₂
#align continuous_within_at.clog ContinuousWithinAt.clog

theorem ContinuousOn.clog {f : α → ℂ} {s : Set α} (h₁ : ContinuousOn f s)
    (h₂ : ∀ x ∈ s, 0 < (f x).re ∨ (f x).im ≠ 0) : ContinuousOn (fun t => log (f t)) s := fun x hx =>
  (h₁ x hx).clog (h₂ x hx)
#align continuous_on.clog ContinuousOn.clog

theorem Continuous.clog {f : α → ℂ} (h₁ : Continuous f) (h₂ : ∀ x, 0 < (f x).re ∨ (f x).im ≠ 0) :
    Continuous fun t => log (f t) :=
  continuous_iff_continuousAt.2 fun x => h₁.ContinuousAt.clog (h₂ x)
#align continuous.clog Continuous.clog

end LogDeriv


import data.nat.prime
import data.real.basic
-- import data.nat.gcd

open nat

-- 1º ejemplo
-- ==========

-- 1ª demostración
example : ∃ x : ℝ, 2 < x ∧ x < 4 :=
⟨5/2, by norm_num, by norm_num⟩

-- 2ª demostración
example : ∃ x : ℝ, 2 < x ∧ x < 4 :=
begin
  use 5 / 2,
  split; norm_num
end

-- 2º ejemplo
-- ==========

-- 1ª demostración
example (x y : ℝ) : (∃ z : ℝ, x < z ∧ z < y) → x < y :=
begin
  rintros ⟨z, xltz, zlty⟩,
  exact lt_trans xltz zlty
end

-- 2ª demostración
example (x y : ℝ) : (∃ z : ℝ, x < z ∧ z < y) → x < y :=
λ ⟨z, xltz, zlty⟩, lt_trans xltz zlty

-- 3º ejemplo
-- ==========

example : ∃ m n : ℕ,
  4 < m ∧ m < n ∧ n < 10 ∧ prime m ∧ prime n :=
begin
  use [5, 7],
  norm_num,
end

-- 4º ejemplo
-- ==========

example {x y : ℝ} : x ≤ y ∧ x ≠ y → x ≤ y ∧ ¬ y ≤ x :=
begin
  rintros ⟨h₀, h₁⟩,
  use [h₀, λ h', h₁ (le_antisymm h₀ h')]
end

-- 2ª demostración
example {x y : ℝ} : x ≤ y ∧ x ≠ y → x ≤ y ∧ ¬ y ≤ x :=
begin
  rintros ⟨h₀, h₁⟩,
  split,
    exact h₀,
  contrapose ! h₁,
  apply le_antisymm h₀ h₁
end


import tactic
import analysis.special_functions.trigonometric.arctan

.

namespace new

-- def complex : Type := ℝ × ℝ

structure complex : Type :=
(r : ℝ)
(im : ℝ)

structure polar : Type :=
(angle : ℝ)
(magnitude : ℝ)
-- (mag_nn : magnitude ≥ 0)

def i : complex := {r := 0, im := 1}

def add (a b : complex) : complex :=
{ r := a.r + b.r,
  im := a.im + b.im }

-- (a + b*i) * (c + d*i)
-- real part: ac - bd

def mul (a b : complex) : complex :=
{ r := a.r * b.r - a.im * b.im,
  im := a.r * b.im + a.im * b.r }

def neg (a : complex) : complex :=
{ r := -a.r, im := -a.im }

-- more fields to fill in here
instance : comm_ring complex :=
{ add := add,
  neg := neg,
  mul := mul,
  add_assoc := begin
    intros a b c,
    simp [add],
    apply and.intro;
    ring
  end,
  zero := {r := 0, im := 0},
  zero_add := begin intro a, simp [add], cases a, refl end   }

example (a b c : complex) : a*b + c = c + b*a - a + a :=
by ring

lemma mul_def (a b : complex) : a * b =
  { r := a.r * b.r - a.im * b.im,
    im := a.r * b.im + a.im * b.r } :=
rfl

noncomputable def to_polar_rep (a : complex) : polar :=
{ angle := real.arctan (a.im / a.r),
  magnitude := real.sqrt (a.r^2 + a.im^2)  }

noncomputable def of_polar_rep (p : polar) : complex :=
{ r := p.magnitude * real.cos p.angle,
  im := p.magnitude * real.sin p.angle }

theorem to_and_from (p : polar)
  (hm : p.magnitude > 0)
  (ha1 : -(real.pi / 2) < p.angle)
  (ha2 : p.angle < real.pi / 2)
  :
  to_polar_rep (of_polar_rep p) = p :=
begin
  cases p,
  simp [to_polar_rep, of_polar_rep],
  apply and.intro,
  { rw [mul_div_mul_left, ← real.tan_eq_sin_div_cos, real.arctan_tan],
    assumption,
    assumption,
    linarith },
  { suffices : (p_magnitude * real.cos p_angle) ^ 2 +
     (p_magnitude * real.sin p_angle) ^ 2 = p_magnitude^2,
     { rw [this, real.sqrt_sq], linarith },
      have : (real.sin p_angle) ^2 + (real.cos p_angle) ^ 2 = 1 :=
        real.sin_sq_add_cos_sq _,
      linear_combination p_magnitude ^ 2 * this }
end

instance : has_coe ℝ complex :=
 { coe := λ r, {r := r, im := 0} }

@[simp] lemma im_coe (r : ℝ) : (r : complex).im = 0 :=
rfl

@[simp] lemma re_coe (r : ℝ) : (r : complex).r = r :=
rfl

theorem to_polar_real (r : ℝ) (hr : r > 0) :
  (to_polar_rep r).angle = 0 :=
begin
  simp only [to_polar_rep, im_coe, zero_div, real.arctan_zero],
end

def conjugate (c : complex) : complex :=
{ r := c.r,
  im := -c.im }

def reals : set complex :=
{ c : complex | c.im = 0 }

lemma mul_conjugate_is_real (c : complex) :
  c * conjugate c ∈ reals :=
begin
  simp [reals, conjugate, mul_def],
  ring,
end

noncomputable def magnitude (c : complex) : ℝ :=
real.sqrt ((c * conjugate c).r)

variables s t : set complex
variable c : complex

#check c ∈ s
#check s c

#check s ∩ t
-- λ x, s x ∧ t x

#check set.mem_inter

#check s ∪ t
-- λ x, s x ∨ t x

end new


section propositional

variables P Q R : Prop

------------------------------------------------
-- Proposições de dupla negaço:
------------------------------------------------

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro hp,
  intro np,
  exact np hp,
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro nnp,
  by_contradiction pboom,
  exact nnp pboom,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
    exact doubleneg_elim P,
    exact doubleneg_intro P,
end

------------------------------------------------
-- Comutatividade dos ∨,∧:
------------------------------------------------

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro poq,
  cases poq with hp hq,
    right,
      exact hp,
    left,
      exact hq,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro peq,
  split,
    cases peq with hp hq,
      exact hq,
    cases peq with hp hq,
      exact hp,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos →,∨:
------------------------------------------------

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intros npoq hp,
  cases npoq with np hq,
    contradiction,
    exact hq,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intros poq np,
  cases poq with hp hq,
    contradiction,
    exact hq,
end

------------------------------------------------
-- Proposições de contraposição:
------------------------------------------------

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intros piq nq hp,
  have hq: Q := piq hp,
  exact nq hq,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intros nqinp hp,
  by_contradiction nq,
  have np: ¬P := nqinp nq,
  exact np hp,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  split,
    exact impl_as_contrapositive P Q,
    exact impl_as_contrapositive_converse P Q,
end

------------------------------------------------
-- A irrefutabilidade do LEM:
------------------------------------------------

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro n_ponp,
  have ponp: (P∨¬P),
    right,
    intro hp,
    apply n_ponp,
      left,
      exact hp,
  exact n_ponp ponp,
end

------------------------------------------------
-- A lei de Peirce
------------------------------------------------

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intros piq_ip np,
  have piq: (P → Q),
    intro hp,
    contradiction,
  have p:P  := piq_ip piq,
  exact np p,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∨,∧:
------------------------------------------------

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intros poq npenq,
  cases poq with hp hq,
    cases npenq with np nq,
      exact np hp,
    cases npenq with np nq,
      exact nq hq,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intros peq nponq,
  cases nponq with np nq,
    cases peq with hp hq,
      exact np hp,
    cases peq with hp hq,
      exact nq hq,
end

------------------------------------------------
-- As leis de De Morgan para ∨,∧:
------------------------------------------------

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro n_poq,
  split,
    intro hp,
      have poq: (P∨Q),
        left,
        exact hp,
      exact n_poq poq,
    intro hq,
      have poq: (P∨Q),
        right,
        exact hq,
      exact n_poq poq,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intros npenq poq,
  cases poq with hp hq,
    cases npenq with np nq,
      exact np hp,
    cases npenq with np nq,
      exact nq hq,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_peq,
  by_contradiction nq_o_np,
  apply nq_o_np,
    right,
    intro hp,
  apply nq_o_np,
    left,
    intro hq,
  exact n_peq ⟨hp,hq⟩,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intros nqonp peq,
  cases nqonp with nq np,
    cases peq with hp hq,
      exact nq hq,
    cases peq with hp hq,
      exact np hp,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
    exact demorgan_conj P Q,
    exact demorgan_conj_converse P Q,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
    exact demorgan_disj P Q,
    exact demorgan_disj_converse P Q,
end

------------------------------------------------
-- Proposições de distributividade dos ∨,∧:
------------------------------------------------

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro pe_qor,
  cases pe_qor with hp qor,
    cases qor with hq hr,
      left,
      split,
        exact hp,
        exact hq,
      right,
      split,
        exact hp,
        exact hr,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro peq_o_per,
  split,
    cases peq_o_per with peq per,
      cases peq with hp hq,
        exact hp,
      cases per with hp hr,
        exact hp,
    cases peq_o_per with peq per,
      left,
        cases peq with hp hq,
          exact hq,
      right,
        cases per with hp hr,
          exact hr,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro po_qer,
  split,
    cases po_qer with hp qer,
      left,
        exact hp,
      cases qer with hq hr,
        right,
          exact hq,
    cases po_qer with hp qer,
      left,
        exact hp,
      cases qer with hq hr,
        right,
          exact hr,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro poq_e_por,
  cases poq_e_por with poq por,
    cases poq with hp hq,
      left,
        exact hp,
      cases por with hp hr,
        left,
          exact hp,
        right,
          split,
            exact hq,
            exact hr,
end

------------------------------------------------
-- Currificação
------------------------------------------------

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intros peq_ir hp hq,
  exact peq_ir ⟨hp,hq⟩,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intros pi_qir peq,
  cases peq with hp hq,
    exact pi_qir hp hq,
end

------------------------------------------------
-- Reflexividade da →:
------------------------------------------------

theorem impl_refl :
  P → P  :=
begin
  intro hp,
  exact hp,
end

------------------------------------------------
-- Weakening and contraction:
------------------------------------------------

theorem weaken_disj_right :
  P → (P∨Q)  :=
begin
  intro hp,
  left,
    exact hp,
end

theorem weaken_disj_left :
  Q → (P∨Q)  :=
begin
  intro hq,
  right,
    exact hq,
end

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro peq,
  cases peq with hp hq,
    exact hp,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro peq,
  cases peq with hp hq,
    exact hq,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
    exact weaken_conj_right P P,
    intro hp,
    split,
      exact hp,
      exact hp,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
    intro pop,
    cases pop with hp hp,
      exact hp,
      exact hp,
    exact weaken_disj_right P P,
end

end propositional

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

section predicate

variable U : Type
variables P Q : U -> Prop

------------------------------------------------
-- As leis de De Morgan para ∃,∀:
------------------------------------------------

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intros n_eapa a hp,
  exact n_eapa ⟨a,hp⟩,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intros a_npa eapa,
  cases eapa with a pa,
    exact a_npa a pa,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro n_apa,
  by_contradiction ea_npa,
  apply n_apa,
    intro a,
    by_contradiction npa,
  apply ea_npa,
    existsi a,
    intro pa,
  exact npa pa,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intros ea_npa apa,
  cases ea_npa with a npa,
  have pa : P a := apa a,
  exact npa pa,
end

theorem demorgan_forall_law :
  ¬(∀x, P x) ↔ (∃x, ¬P x)  :=
begin
  split,
    apply demorgan_forall,
    apply demorgan_forall_converse,
end

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
    apply demorgan_exists,
    apply demorgan_exists_converse,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∃,∀:
------------------------------------------------

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intros eapa a_npa,
  cases eapa with a pa,
  exact a_npa a pa,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intros apa ea_npa,
  cases ea_npa with a npa,
  have pa : P a := apa a,
  exact npa pa,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intros n_ea_npa a,
  by_contradiction np,
  exact n_ea_npa ⟨ a,np ⟩,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro n_a_npa,
  by_contradiction eapa,
  apply n_a_npa,
    intro a,
    intro pa,
  apply eapa,
    existsi a,
    exact pa,
end

theorem forall_as_neg_exists_law :
  (∀x, P x) ↔ ¬(∃x, ¬P x)  :=
begin
  split,
    apply forall_as_neg_exists,
    apply forall_as_neg_exists_converse,
end

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
    apply exists_as_neg_forall,
    apply exists_as_neg_forall_converse,
end

------------------------------------------------
--  Proposições de distributividade de quantificadores:
------------------------------------------------

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro ea_paeqa,
  split,
    cases ea_paeqa  with a paeqa,
      existsi a,
      cases paeqa with pa qa,
        exact pa,
    cases ea_paeqa with a paeqa,
      existsi a,
      cases paeqa with pa qa,
        exact qa,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro ea_paoqa,
  cases ea_paoqa with a paoqa,
  cases paoqa with pa qa,
    left,
      existsi a,
      exact pa,
    right,
      existsi a,
      exact qa,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro eapa_o_eaqa,
  cases eapa_o_eaqa with eapa eaqa,
    cases eapa with a pa,
      existsi a,
      left,
        exact pa,
    cases eaqa with a qa,
      existsi a,
      right,
        exact qa,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro a_paeqa,
  split,
    intro a,
    have paeqa : P a ∧ Q a := a_paeqa a,
    cases paeqa with pa qa,
      exact pa,
    intro a,
    have paeqa : P a ∧ Q a := a_paeqa a,
    cases paeqa with pa qa,
      exact qa,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intros apa_e_aqa a,
  cases apa_e_aqa with apa aqa,
    split,
      exact apa a,
      exact aqa a,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intros apa_o_aqa a,
  cases apa_o_aqa with apa aqa,
    left,
      exact apa a,
    right,
      exact aqa a,
end

/- NOT THEOREMS --------------------------------

theorem forall_disj_as_disj_forall :
  (∀x, P x ∨ Q x) → (∀x, P x) ∨ (∀x, Q x)  :=
begin
end

theorem exists_conj_as_conj_exists_converse :
  (∃x, P x) ∧ (∃x, Q x) → (∃x, P x ∧ Q x)  :=
begin
end

---------------------------------------------- -/

end predicate


-- Mathematical induction is the process of, by simple rules,
-- we achieve a generalization (the opposite of deduction).
-- Inductive data types follows the same principles, they
-- have a base case and a inductive case.

inductive ℕ : Type where
  | z : ℕ      -- Here "z" (zero) is the base case
  | s : ℕ → ℕ  -- And "s" (sucessor) is the inductive case

theorem seven : ℕ := ℕ.s (ℕ.s (ℕ.s (ℕ.s (ℕ.s (ℕ.s (ℕ.s ℕ.z))))))

-- Some cool definitions to make it easier to work with this new
-- data type

def fromDefaultNat : Nat → ℕ
  | 0   => ℕ.z
  | n+1 => ℕ.s (fromDefaultNat n)

def toDefaultNat : ℕ → Nat
  | ℕ.z   => 0
  | ℕ.s n => Nat.succ (toDefaultNat n)

instance : OfNat ℕ n where ofNat := fromDefaultNat n
instance : Repr ℕ where reprPrec x _ := repr (toDefaultNat x)

-- Operations are defined recursively like that

def add : ℕ → ℕ → ℕ
  | ℕ.z,   m => m
  | ℕ.s n, m => ℕ.s (add n m)

def add' : ℕ → ℕ → ℕ
  | m, ℕ.z   => m
  | m, ℕ.s n => ℕ.s (add' m n)

def mult : ℕ → ℕ → ℕ
  | ℕ.z,   m    => ℕ.z
  | ℕ.s ℕ.z,  m => m
  | ℕ.s n, m    => add m (mult n m)

-- Like subtraction but trim at zero

def monus : ℕ → ℕ → ℕ
  | ℕ.z,       m => ℕ.z
  | m,       ℕ.z => m
  | ℕ.s m, ℕ.s n => monus m n

infixl:65   " + " => add
infixl:66   " * " => mult

instance : HSub ℕ ℕ ℕ where hSub := monus

example : 3 * 3 + 5 - 4 = 10 := rfl

-- Binary natural numbers

inductive 𝔹 : Type where
  | nob  : 𝔹
  | zero : 𝔹 → 𝔹
  | one  : 𝔹 → 𝔹
  deriving Repr

def Bin.inc : 𝔹 → 𝔹
  | 𝔹.nob    => 𝔹.one 𝔹.nob
  | 𝔹.zero n => 𝔹.one n
  | 𝔹.one  n => 𝔹.zero (inc n)

def Bin.toNat : 𝔹 → ℕ
  | 𝔹.nob    => ℕ.z
  | 𝔹.zero n => toNat n * 2
  | 𝔹.one  n => toNat n * 2 + 1

def Nat.toBin : ℕ → 𝔹
  | ℕ.z   => 𝔹.nob
  | ℕ.s n => Bin.inc (toBin n)

instance : OfNat 𝔹 n where ofNat := Nat.toBin (fromDefaultNat n)

-- Proofs

theorem Nt.Add.sucAssoc : ∀ (m n : ℕ), ℕ.s m + n = ℕ.s (m + n)
  | ℕ.z, n   => rfl
  | ℕ.s m, n => congrArg ℕ.s (sucAssoc m n)

theorem Nt.Add.sucShift : ∀ (m n : ℕ), ℕ.s m + n = m + ℕ.s n
  | ℕ.z, n => rfl
  | ℕ.s m, n => Eq.trans (Nt.Add.sucAssoc (ℕ.s m) n) (congrArg ℕ.s (sucShift m n))

theorem Nt.Add.assoc : ∀ (m n p : ℕ), (m + n) + p = m + (n + p)
  | ℕ.z  , n, p => rfl
  | ℕ.s m, n, p => by
    rw    [Nt.Add.sucAssoc]
    apply congrArg ℕ.s (assoc m n p)

theorem Nt.Add.rightId : ∀ (m : ℕ), m + 0 = m
  | ℕ.z   => rfl
  | ℕ.s n => congrArg ℕ.s (rightId n)

theorem Nt.Add.comm : ∀ (m n : ℕ), m + n = n + m
  | 0,   n => Eq.symm (Nt.Add.rightId n)
  | ℕ.s m, n => by
    rw [Nt.Add.sucAssoc m n, comm m n, ← Nt.Add.sucShift n m]
    rfl

theorem Nt.Mul.leftId : ∀ (m : ℕ), ℕ.s 0 * m = m
  | 0 => rfl
  | ℕ.s m => rfl

theorem Nt.Mul.leftNeutral : ∀ (m : ℕ), 0 * m = 0
  | 0     => rfl
  | ℕ.s m => rfl

theorem Nt.Mul.sucLeft : ∀ (m n : ℕ), ℕ.s m * n = n + (m * n)
  | 0, n     => by apply (Eq.symm (Nt.Add.rightId n))
  | ℕ.s m, n => by rfl

theorem Nt.Mul.sucRight : ∀ (m n : ℕ), m * ℕ.s n = (m * n) + m
  | 0, n     => by simp; rfl
  | ℕ.s m, n => by rw [Nt.Mul.sucLeft m, Nt.Mul.sucLeft, sucRight m n, Nt.Add.assoc, Nt.Add.sucShift, ←Nt.Add.sucAssoc, Nt.Add.sucShift];

theorem Nt.Mul.distribAdd : ∀ (m n p : ℕ), (m + n) * p = m * p + n * p
  | 0  , n, p   => by rfl
  | ℕ.s m, n, p => by
    rw [Nt.Add.sucAssoc]
    simp [Nt.Mul.sucLeft]
    rw [distribAdd m n p, Nt.Add.assoc]

theorem Nt.Mul.rightNeutral : ∀ (m : ℕ), m * 0 = 0
  | 0     => rfl
  | ℕ.s m => by rw[Nt.Mul.sucLeft, rightNeutral m]; rfl

theorem Nt.Mul.rightId : ∀ (m : ℕ), m * ℕ.s 0 = m
  | 0     => rfl
  | ℕ.s m => by rw[Nt.Mul.sucLeft, rightId m]; rfl

theorem Nt.Mul.comm :  ∀ (m n : ℕ), m * n = n * m
  | 0  ,   n => Eq.symm (Nt.Mul.rightNeutral n)
  | ℕ.s m, n => by rw [Nt.Mul.sucRight, Nt.Mul.sucLeft, Nt.Add.comm, comm m n]

termination_by
  Nt.Mul.comm m _ => sizeOf m

-- Binary

theorem Bin.incEqNatSuc : ∀ (b : 𝔹), Bin.toNat (Bin.inc b) = ℕ.s (Bin.toNat b)
  | 𝔹.nob => rfl
  | 𝔹.zero n => by simp [Bin.toNat]; rw [Nt.Add.comm]; rfl
  | 𝔹.one n => by
    simp [Bin.toNat]
    rw [Nt.Add.comm, incEqNatSuc n, Nt.Mul.sucLeft]
    rfl

termination_by
  Bin.incEqNatSuc t => sizeOf t

theorem Bin.eqNat : ∀ (n : ℕ), Bin.toNat (Nat.toBin n) = n
  | 0     => rfl
  | ℕ.s n => by simp [Nat.toBin]; rw [Bin.incEqNatSuc, eqNat n];

termination_by
  Bin.eqNat t => sizeOf t


import linear_algebra.basic
import linear_algebra.basis
open linear_map
open is_basis
universe variables u v w
variables {R : Type v}  [comm_ring R]
          {X : Type w} [fintype X] [decidable_eq X]

open_locale big_operators
/--
    change a map by zero outside a `finset s`.
-/
def updapte_with_zero ( φ : X → R) (s : finset X) : X → R := λ x, if x ∈ s then φ x else 0
/--
   `(updapte_with_zero φ s) x • B x = if x ∈ s then  φ x • B x else 0`
-/
lemma updapte_with_zero_smul ( φ : X → R) (s : finset X) (M : Type u)[add_comm_group M] [ module R M] (B : X → M) :
(λ x : X, (updapte_with_zero φ s) x • B x) = (λ x : X, if x ∈ s then  φ x • B x else 0) :=
begin
    funext, unfold updapte_with_zero,split_ifs, exact rfl,
    rw zero_smul,
end
/--
    Helper for finite linear_independance
    if `∀ φ : X → R,  (∑  x, φ x • B x = 0) → ∀ x : X, φ x = 0 )` then  `linear_independent R B`
-/
variables ( φ : X → R)
lemma linear_independent_finite (M : Type u)[add_comm_group M] [ module R M] (B : X → M) :
(∀ φ : X → R, (∑  x, φ x • B x = 0) → ∀ x : X, φ x = 0 )  →  linear_independent R B :=
begin
    intros hyp_ind,
    rw linear_independent_iff',
    intros s φ hyp x hyp_x,
    --let F := hyp_ind φ,
    specialize hyp_ind (updapte_with_zero φ s), rw updapte_with_zero_smul at hyp_ind,
    have R : ∑ x, ite (x ∈ s) (φ x • B x) 0 = ∑ i in  s, φ i • B i,
        rw finset.sum_ite, rw finset.sum_const_zero, rw add_zero,
        congr, exact finset.univ_inter s,
    rw hyp at R,
    specialize hyp_ind R x,rw ← hyp_ind,
    unfold updapte_with_zero,
    split_ifs,exact rfl,
end
/--
    Helper for finite span
    if `∀ m :  M, ∃ φ : X → R, m =  ∑  x , (φ x) • (B x)` then
     `submodule.span R (set.range B) = ⊤`
-/
lemma gen_finite (M : Type u)[add_comm_group M] [ module R M] (B : X → M) :
(∀ m :  M, ∃ φ : X → R, m =  ∑  x , (φ x) • (B x)) → submodule.span R (set.range B) = ⊤ :=
begin
    intros hyp,rw eq_top_iff, rw submodule.le_def',
    intros m, intros,
    specialize hyp m,
    rcases hyp with ⟨φ , hyp_phi ⟩,
    rw hyp_phi,
    let p := submodule.span R (set.range B),
    apply submodule.sum_mem p ,
    intros x, intros,
    apply submodule.smul_mem p, apply submodule.subset_span, apply set.mem_range_self,
end


/-
Copyright (c) 2021 Benjamin Davidson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Benjamin Davidson
-/

import analysis.special_functions.integrals
open interval_integral real
open_locale real

/-! ### Simple functions -/

/- constants -/
example : ∫ x : ℝ in 8..11, (1 : ℝ) = 3 := by norm_num
example : ∫ x : ℝ in 5..19, (12 : ℝ) = 168 := by norm_num

/- the identity function -/
example : ∫ x : ℝ in (-1)..4, x = 15 / 2 := by norm_num
example : ∫ x : ℝ in 4..5, x * 2 = 9 := by norm_num

/- inverse -/
example : ∫ x : ℝ in 2..3, x⁻¹ = log (3 / 2) := by norm_num

/- natural powers -/
example : ∫ x : ℝ in 2..4, x ^ (3 : ℕ) = 60 := by norm_num

/- trigonometric functions -/
example : ∫ x in 0..π, sin x = 2 := by norm_num
example : ∫ x in 0..π/4, cos x = sqrt 2 / 2 := by simp
example : ∫ x in 0..π, 2 * sin x = 4 := by norm_num
example : ∫ x in 0..π/2, cos x / 2 = 1 / 2 := by simp
example : ∫ x : ℝ in 0..1, 1 / (1 + x ^ 2) = π / 4 := by simp
example : ∫ x in 0..2*π, sin x ^ 2 = π := by simp [mul_div_cancel_left]
example : ∫ x in 0..π/2, cos x ^ 2 / 2 = π / 8 := by norm_num [div_div_eq_div_mul]
example : ∫ x in 0..π, cos x ^ 2 - sin x ^ 2 = 0 := by simp [integral_cos_sq_sub_sin_sq]
example : ∫ x in 0..π/2, sin x ^ 3 = 2 / 3 := by norm_num
example : ∫ x in 0..π/2, cos x ^ 3 = 2 / 3 := by norm_num
example : ∫ x in 0..π, sin x * cos x = 0 := by simp
example : ∫ x in 0..π, sin x ^ 2 * cos x ^ 2 = π / 8 := by simpa using sin_nat_mul_pi 4

/- the exponential function -/
example : ∫ x in 0..2, -exp x = 1 - exp 2 := by simp

/- the logarithmic function -/
example : ∫ x in 1..2, log x = 2 * log 2 - 1 := by { norm_num, ring }

/- linear combinations (e.g. polynomials) -/
example : ∫ x : ℝ in 0..2, 6*x^5 + 3*x^4 + x^3 - 2*x^2 + x - 7 = 1048 / 15 := by norm_num
example : ∫ x : ℝ in 0..1, exp x + 9 * x^8 + x^3 - x/2 + (1 + x^2)⁻¹ = exp 1 + π / 4 := by norm_num

/-! ### Functions composed with multiplication by and/or addition of a constant -/

/- many examples are computable by `norm_num` -/
example : ∫ x in 0..2, -exp (-x) = exp (-2) - 1 := by norm_num
example : ∫ x in 1..2, exp (5*x - 5) = 1/5 * (exp 5 - 1) := by norm_num
example : ∫ x in 0..π, cos (x/2) = 2 := by norm_num
example : ∫ x in 0..π/4, sin (2*x) = 1/2 := by norm_num [mul_div_comm, mul_one_div]
example (ω φ : ℝ) : ω * ∫ θ in 0..π, sin (ω*θ + φ) = cos φ - cos (ω*π + φ) := by simp

/- some examples may require a bit of algebraic massaging -/
example {L : ℝ} (h : L ≠ 0) : ∫ x in 0..2/L*π, sin (L/2 * x) = 4 / L :=
begin
  norm_num [div_ne_zero h, ← mul_assoc],
  field_simp [h, mul_div_cancel],
  norm_num,
end

/- you may need to provide `norm_num` with the composition lemma you are invoking if it has a
  difficult time recognizing the function you are trying to integrate -/
example : ∫ x : ℝ in 0..2, 3 * (x + 1) ^ 2 = 26 :=
  by norm_num [integral_comp_add_right (λ x, x ^ 2)]
example : ∫ x : ℝ in -1..0, (1 + (x + 1) ^ 2)⁻¹ = π / 4 :=
  by simp [integral_comp_add_right (λ x, (1 + x ^ 2)⁻¹)]

/-! ### Compositions of functions (aka "change of variables" or "integration by substitution") -/

/- `interval_integral.integral_comp_mul_deriv` can be used to simplify integrals of the form
  `∫ x in a..b, (g ∘ f) x * f' x`, where `f'` is the derivative of `f`, to `∫ x in f a..f b, g x` -/
example {a b : ℝ} : ∫ x in a..b, exp (exp x) * exp x = ∫ x in exp a..exp b, exp x :=
integral_comp_mul_deriv (λ x hx, has_deriv_at_exp x) continuous_on_exp continuous_exp

/- if it is known (to mathlib), the integral of `g` can then be evaluated using `simp`/`norm_num` -/
example : ∫ x in 0..1, exp (exp x) * exp x = exp (exp 1) - exp 1 :=
by rw integral_comp_mul_deriv (λ x hx, has_deriv_at_exp x) continuous_on_exp continuous_exp; simp

/- a more detailed example -/
example : ∫ x in 0..2, exp (x ^ 2) * (2 * x) = exp 4 - 1 :=
begin                                                    -- let g := exp x, f := x ^ 2, f' := 2 * x
  rw integral_comp_mul_deriv (λ x hx, _),                -- simplify to ∫ x in f 0..f 2, g x
  { norm_num },                                          -- compute the integral
  { exact continuous_on_const.mul continuous_on_id },    -- show that f' is continuous on [0, 2]
  { exact continuous_exp },                              -- show that g is continuous
  { simpa using has_deriv_at_pow 2 x },                  -- show that f' = derivative of f on [0, 2]
end

/- alternatively, `interval_integral.integral_deriv_comp_mul_deriv` can be used to compute integrals
  of this same form, provided that you also know that `g` is the derivative of some function -/
example : ∫ x : ℝ in 0..1, exp (x ^ 2) * (2 * x) = exp 1 - 1 :=
begin
  rw integral_deriv_comp_mul_deriv (λ x hx, _) (λ x hx, has_deriv_at_exp (x^2)) _ continuous_exp,
  { simp },
  { simpa using has_deriv_at_pow 2 x },
  { exact continuous_on_const.mul continuous_on_id },
end


import Mathlib.Tactic.Basic
/-!
## Reasoning about Equality

Equality (`=`) is also a basic logical constant. It is characterized by the following
introduction and elimination rules:

```lean
Eq.refl : ∀a, a = a
Eq.symm : ?a = ?b → ?b = ?a
Eq.trans : ?a = ?b → ?b = ?c → ?a = ?c
Eq.subst : ?a = ?b → ?p ?a → ?p ?b
```

The first three lemmas are introduction rules specifying that `=` is an equivalence
relation. The fourth lemma is an elimination rule that allows us to replace equals
for equals in an arbitrary context, represented by the higher-order variable `?p`.

An example will show how this works. Below, we apply `Eq.subst` to rewrite
`f a b` to `f a' b`, using the hypothesis `a = a'`:
-/

theorem cong_fst_arg {α : Type} (a a' b : α)
(f : α → α → α) (ha : a = a') : f a b = f a' b := by
  apply Eq.subst ha (motive := λ x => f a b = f x b)
  apply Eq.refl

-- BUGBUG why does lean4 require this motive, lean3 did not
-- See https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/debugging.20'apply'.20failed.20to.20unify

/-!
The `Eq.subst` instance we use has `?a := a`, `?b := a'`, and `?p := (λ x => f a b = f x b)`:
```lean
a = a' → (λx => f a b = f x b) a → (λx => f a b = f x b) a'
```
In β-reduced form:
```lean
a = a' → f a b = f a b → f a b = f a' b
```
The lemma's first assumption matches the hypothesis `ha : a = a'`, which is passed as an argument in the
first apply invocation. The lemma's second assumption is a trivial equality that can be proved by
apply `Eq.refl` or `rfl`. The lemma's conclusion matches the goal’s target. Notice how a higher-order
variable (e.g., `?p`) can represent an arbitrary context (e.g., `f . . . b`) around a term (e.g.,
`a` or `a'`). This works because `apply` unifies up to computation, including β-conversion.

The `Eq.subst` lemma can be applied several times in sequence, as follows:

(λx => f x n₁ ... nₖ) a = f a` n₁ ... nₖ

-/

lemma cong_two_args {α : Type} (a a' b b' : α)
  (f : α → α → α) (ha : a = a') (hb : b = b') :
f a b = f a' b' := by
  apply Eq.subst ha (motive := λ x => f a b = f x b')
  apply Eq.subst hb (motive := λ x => f a b = f a x)
  apply Eq.refl
  -- rw [ha, hb]

-- BUGBUG lean3 did not require these explicit motives...

/-!
Since rewriting in this way is such a common operation, Lean provides a `rw` tactic
to achieve the same result. The tactic will also notice if `rfl` is applicable:
-/
lemma cong_two_args₂ {α : Type} (a a' b b' : α)
  (f : α → α → α) (ha : a = a') (hb : b = b') :
f a b = f a' b' := by
  rw [ha]
  rw [hb]

/-!
A note on parsing: Equality binds more tightly than the logical connectives.
Thus, `a = a' ∧ b = b'` should be read as (a = a') ∧ (b = b').

-/


/-
Notes for 11-07-2019.lean
-/

namespace test
-- The and'' proposition is really just like a product inductive type
inductive and'' (α β : Prop) : Prop
| intro : α → β → and''

-- but over the universe of Prop rather than of Type

inductive prod' (α β : Type) : Type
| pair : α → β → prod'

structure and (a b : Prop) : Prop :=
    intro :: (left : a) (right : b)

def and.elim_left {α β : Prop} : and α β → α
| (and.intro a b) := a

def and.elim_right {α β : Prop} : and α β → β
| (and.intro a b) := b

end test

def P1 : Prop := 0 = 0
def P2 : Prop := 1 = 1
def p1 : P1 := eq.refl 0

def P1_and_P2 := P1 ∧ P2

theorem pf : P1_and_P2 :=
begin
    apply and.intro,
    apply eq.refl,
    apply eq.refl
end

inductive Person
| harsh
| joe
| will

inductive friends : Person → Person → Prop
| refl : ∀ (a : Person), friends a a
| symm : ∀ (a b : Person), (friends a b) → (friends b a)
| trans : ∀ (a b c : Person), (friends a b) → (friends b c) → (friends a c)

theorem joe_is_friends_with_self : friends Person.joe Person.joe :=
begin
    apply friends.refl
end

open Person
theorem if_harsh_then_harsh : (friends harsh joe) → (friends joe harsh) :=
begin
    apply friends.symm,
end

def transitive' {α : Type} (r : α → α → Prop) : Prop := ∀ (a b c : α), r a b → r b c → r a c

theorem friends_transitive : transitive' friends :=
begin
    unfold transitive',
    assume (a b c),
    assume ab bc,
    exact friends.trans a b c ab bc,
end

/-
Prove 0=0.

Proof: Equality is reflexive. this means that for any value a of any type α, a = a.

∀ {T : Type}, t → t = t

We apply this axiom to 0 in particular, to derive a proof that 0=0.
-/


import data.fintype data.finset algebra.group_power
open fintype finset
/- 6. Say A and B are finite sets, with sizes a and b respectively. Prove that the set B^A of functions A → B has size b^a. What about the case a = b = 0?
-/

#check finset.card
variables {A B C : Type} [fintype A] [decidable_eq A] [fintype B]
variables {a b : ℕ }
theorem Q1006 (hp: card A = a) (hq : card B = b) : card (A → B) = b^a := sorry


def isEven : ℕ → Prop := λ n, ∃ m : ℕ, m * 2 = n

lemma eightIsEven : isEven 8 :=
begin
    unfold isEven,
    exact ⟨4, dec_trivial⟩,
end

-- def isNonZ : ℕ → Prop := λ n, n ≠ 0

def isnonz : Prop := ∃ n : ℕ, n ≠ 0

lemma isNonZ : isnonz := ⟨1, dec_trivial⟩

lemma isNonZ' : isnonz :=
begin
    apply exists.intro 1,
    assume : 1 = 0,
    show false, from nat.no_confusion this,
end

example : ∀ {P S : ℕ → Prop},
    (∃ n : ℕ, P n ∧ S n) → (∃ n : ℕ, S n ∧ P n) :=
begin
    assume P S,
    assume ex,
    apply exists.elim ex,
        assume x,
        assume px_sx,
        apply exists.intro x,
            apply and.comm.mpr,
            assumption,
end

example : ∀ {P S : ℕ → Prop},
    (∃ n : ℕ, P n ∧ S n) → (∃ n : ℕ, S n ∧ P n) :=
begin
    assume P S,
    assume ex,
    apply exists.elim ex,
        assume x,
        assume px_sx,
        apply exists.intro x,
            apply and.intro,
                exact px_sx.2,
                exact px_sx.1,
end

def isSquare : ℕ → Prop := λ n, ∃ m : ℕ, n * n = m

example : isSquare 9 := ⟨81, dec_trivial⟩

example : isSquare 9 :=
begin
    apply exists.intro 81,
    apply rfl
end


-- Suma de no negativos (en Lean)
-- ==============================

-- Demostrar si a y b son números reales no negativos,
-- entonces a + b es no negativo.

import data.real.basic

variables (a b : ℝ)

-- 1ª demostración
example
  (ha : 0 ≤ a)
  (hb : 0 ≤ b)
  : 0 ≤ a + b :=
begin
  calc 0 ≤ a     : ha
     ... ≤ a + b : le_add_of_nonneg_right hb,
end

-- 2ª demostración
example
  (ha : 0 ≤ a)
  (hb : 0 ≤ b)
  : 0 ≤ a + b :=
add_nonneg ha hb

-- 3ª demostración
example
  (ha : 0 ≤ a)
  (hb : 0 ≤ b)
  : 0 ≤ a + b :=
by linarith


-- Monotonia_de_la_multiplicacion_por_no_positivo.lean
-- Monotonía de la multiplicación por no positivo.
-- José A. Alonso Jiménez
-- Sevilla, 22 de agosto de 2020
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si a, b y c son números reales tales que
-- c ≤ 0 y a ≤ b, entonces b*c ≤ a*c.
-- ----------------------------------------------------------------------

import data.real.basic

variables {a b c : ℝ}

-- 1ª demostración
-- ===============

example
  (hc : c ≤ 0)
  (hab : a ≤ b)
  : b * c ≤ a * c :=
begin
  rw ← sub_nonneg,
  have h : a * c - b * c = (a - b) * c,
  { ring },
  { rw h,
    apply mul_nonneg_of_nonpos_of_nonpos,
    { rwa sub_nonpos, },
    { exact hc, }},
end

-- Comentario: Se ha usado los lemas
-- + mul_nonneg_of_nonpos_of_nonpos : a ≤ 0 → b ≤ 0 → 0 ≤ a * b
-- + sub_nonpos : a - b ≤ 0 ↔ a ≤ b

-- 2ª demostración
-- ===============

example
  (hc : c ≤ 0)
  (hab : a ≤ b)
  : b * c ≤ a * c :=
begin
  have hab' : a - b ≤ 0,
  { rwa ← sub_nonpos at hab, },
  have h1 : 0 ≤ (a - b) * c,
  { exact mul_nonneg_of_nonpos_of_nonpos hab' hc, },
  have h2 : (a - b) * c = a * c - b * c,
  { ring, },
  have h3 : 0 ≤ a * c - b * c,
  { rwa h2 at h1, },
  rwa sub_nonneg at h3,
end

-- 3ª demostración
-- ===============

example
  (hc : c ≤ 0)
  (hab : a ≤ b)
  : b * c ≤ a * c :=
begin
  rw ← sub_nonneg,
  have hab' : a - b ≤ 0,
  { rwa sub_nonpos, },
  calc 0 ≤ (a - b)*c  : mul_nonneg_of_nonpos_of_nonpos hab' hc
     ... =  a*c - b*c : by ring,
end

-- 4ª demostración
-- ===============

example
  (hc : c ≤ 0)
  (hab : a ≤ b)
  : b * c ≤ a * c :=
mul_mono_nonpos hc hab

-- Comentario: Se usa el lema
-- + mul_mono_nonpos : 0 ≥ c → b ≤ a → a * c ≤ b * c

-- 5ª demostración
-- ===============

example
  (hc : c ≤ 0)
  (hab : a ≤ b)
  : b * c ≤ a * c :=
by nlinarith


/-
Copyright (c) 2021 Matt Kempster. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Matt Kempster
-/
import geometry.euclidean.triangle

/-!
# Freek № 57: Heron's Formula

This file proves Theorem 57 from the [100 Theorems List](https://www.cs.ru.nl/~freek/100/),
also known as Heron's formula, which gives the area of a triangle based only on its three sides'
lengths.

## References

* https://en.wikipedia.org/wiki/Herons_formula

-/

open real euclidean_geometry
open_locale real euclidean_geometry

local notation `√` := real.sqrt

variables {V : Type*} {P : Type*} [inner_product_space ℝ V] [metric_space P]
  [normed_add_torsor V P]

include V

/-- **Heron's formula**: The area of a triangle with side lengths `a`, `b`, and `c` is
  `√(s * (s - a) * (s - b) * (s - c))` where `s = (a + b + c) / 2` is the semiperimeter.
  We show this by equating this formula to `a * b * sin γ`, where `γ` is the angle opposite
  the side `c`.
 -/
theorem heron {p1 p2 p3 : P} (h1 : p1 ≠ p2) (h2 : p3 ≠ p2) :
  let a := dist p1 p2, b := dist p3 p2, c := dist p1 p3, s := (a + b + c) / 2 in
  1/2 * a * b * sin (∠ p1 p2 p3) = √(s * (s - a) * (s - b) * (s - c)) :=
begin
  intros a b c s,
  let γ := ∠ p1 p2 p3,
  obtain := ⟨(dist_pos.mpr h1).ne', (dist_pos.mpr h2).ne'⟩,
  have cos_rule : cos γ = (a * a + b * b - c * c) / (2 * a * b) := by field_simp [mul_comm, a,
    dist_sq_eq_dist_sq_add_dist_sq_sub_two_mul_dist_mul_dist_mul_cos_angle p1 p2 p3],
  let numerator := (2*a*b)^2 - (a*a + b*b - c*c)^2,
  let denominator := (2*a*b)^2,
  have split_to_frac : 1 - cos γ ^ 2 = numerator / denominator := by field_simp [cos_rule],
  have numerator_nonneg : 0 ≤ numerator,
  { have frac_nonneg: 0 ≤ numerator / denominator := by linarith [split_to_frac, cos_sq_le_one γ],
    cases div_nonneg_iff.mp frac_nonneg,
    { exact h.left },
    { simpa [h1, h2] using le_antisymm h.right (sq_nonneg _) } },
  have ab2_nonneg : 0 ≤ (2 * a * b) := by norm_num [mul_nonneg, dist_nonneg],
  calc  1/2 * a * b * sin γ
      = 1/2 * a * b * (√numerator / √denominator) : by rw [sin_eq_sqrt_one_sub_cos_sq,
                                                          split_to_frac, sqrt_div numerator_nonneg];
                                                      simp [angle_nonneg, angle_le_pi]
  ... = 1/4 * √((2*a*b)^2 - (a*a + b*b - c*c)^2)  : by { field_simp [ab2_nonneg], ring }
  ... = 1/4 * √(s * (s-a) * (s-b) * (s-c) * 4^2)  : by { simp only [s], ring_nf }
  ... = √(s * (s-a) * (s-b) * (s-c))              : by rw [sqrt_mul', sqrt_sq, div_mul_eq_mul_div,
                                                          one_mul, mul_div_cancel];
                                                      norm_num,
end


import game.limits.seq_convCauchy

namespace xena -- hide

notation `|` x `|` := abs x -- hide

/-
Another useful result for working with sequences -- a convergent sequence is bounded.
-/

/-
We define the concepts of a bounded sequence, in much the same way
that we defined the concepts of a limit and a convergent sequence.
-/

/-
NOTE: limits/seq_cauchyBdd.lean introduces the definition

is_bdd (a : ℕ → ℝ) := ∃ B > 0, ∀ n, |a n| ≤ B

Here I omit the B > 0 condition (which follows from properties of abs?)
But this level is perhaps superfluous anyway.
-/

definition is_bound (a : ℕ → ℝ) (M : ℝ) := ∀ n, | a n | ≤ M
definition is_bounded (a : ℕ → ℝ) := ∃ M, is_bound a M

/- Lemma
A convergent sequence is bounded.
-/
open finset
lemma bounded_if_convergent (a : ℕ → ℝ)
    (ha : is_convergent a ) :
    is_bounded a :=
begin

cases ha with α islim,
specialize islim 1 (by linarith),  -- specialise to the case ε = 1, linarith knows this is OK
cases islim with N_k HN_k,         -- introduce the natural N_k fulfilling ε = 1 case

   -- We use these to show that there is a bound for all terms with index ≥ N_k
   -- Then take the max of this bound and the earlier terms.

have bounds_geq_N_k : ∀ n ≥ N_k, |a n| ≤ | α | + 1,

{
intros bigN Nisbig,                 -- introduce some bigN ≥ N_k
specialize HN_k bigN,               -- specialize to bigN
have fact1:= HN_k(Nisbig),          -- just applying our conditional
rw abs_lt at fact1,

rw abs_le,     -- note our abs_le will require us to show c ≥ 0
simp,
split,

    {have smallfact: - α ≤ | α | := neg_le_abs_self α,
        linarith},

    {have smallfact2: α ≤ | α | := le_abs_self(α),
        linarith},

-- condition for abs_le
have fact: 0 ≤ |α|, from abs_nonneg α,
linarith,

},

   --  Having shown that we have a bound for the elements above N_k, we consider the earlier terms
   --  This section is mostly cribbed from Kevin's M1P1 limits file

   let rangeset := finset.image (abs ∘ a) (range (N_k + 1)),    -- range (N) is set of nats < N

   have fact4 : |a 0| ∈ rangeset := finset.mem_image_of_mem _ (mem_range.2 (nat.zero_lt_succ _)),
   have fact5 : rangeset ≠ ∅ := ne_empty_of_mem fact4,
   have fact6 := nonempty_iff_ne_empty.mpr fact5,

   let Bx := rangeset.max' fact6,
   let B := max Bx ( |α|  + 1),

use B,
intro n,
cases lt_or_ge n N_k with Hlt Hge,

    simp,
    left,
    have HBx : ∀ n < N_k, |a n| ≤ Bx := λ n Hn, le_max' rangeset fact6 _
    (mem_image_of_mem _ (mem_range.2 (nat.lt_succ_of_lt Hn))),
    apply HBx,
    exact Hlt,

    have h2 : |a n| ≤ |α| + 1 := bounds_geq_N_k n Hge,
    have h3 : |α| + 1 ≤ B := le_max_right _ _,
    linarith,
end

end xena -- hide


/-
Intuitively, if a proposition P is true and a
second proposition Q is true, then the more
complex proposition, "P and Q" is also true.
This proposition is written symbolically as
P ∧ Q. In mathematical logic, we say that
the proposition, P ∧ Q, is the conjunction
of the individual propositions, P and Q.

There's an inference rule for that. It says
that if P and Q are propositions (types of
type Prop), and if you have a proof of P and
you have a proof of Q then you can derive a
proof of P ∧ Q. Moreover, the form of that
proof is basically the pair of proofs, (p, q).

{P Q : Prop }, p : P, q : Q
--------------------------- (∧-intro)
        (p, q): P ∧ Q

To give us some propositions and proofs to
use in examples, let's define P and Q to be
simple equality propositions, and let's then
define p and q to be proofs of P and Q.
-/

def P : Prop := 0 = 0
def Q := 1 = 1 -- type inference works here

theorem pfP : P := eq.refl 0
theorem pfQ : Q := eq.refl 1

/-
We note that rfl doesn't work here. It's
the right idea, but the proposition, P,
to be proved isn't written as an equality,
so rfl doesn't know what to do with it.
We just just eq.refl directly instead.
-/

/- * SYNTAX OF CONJUNCTIONS * -/

/--/
Now the syntax of proposition logic allows
us to form a new proposition, P ∧ Q.
-/

def P_and_Q : Prop := P ∧ Q

#check P_and_Q -- it's a proposition
#check P ∧ Q   -- same thing

/- * SEMANTICS OF CONJUNCTIONS * -/

/- * AND INTRODUCTION RULE * -/

/-
Now intuitively, P ∧ Q should be true if
and only if P is true and Q is true, which
in the logic of Lean is to say if we have
a proof of P and a proof of Q. The and.intro
inference rule formalizes this idea. It says
if you give me propositions, P and Q, and
proofs, p and q of P and Q respectively,
then I will give you back a proof of P ∧ Q.

  { P, Q : Prop } (p: P) (q: Q)
  ----------------------------- and.intro
          pf_PQ : P ∧ Q

The and.intro takes two explicit arguments: a
proof of P and a proof of Q. It infers P and Q from pf_P and pf_Q. It then derives a proof of
P ∧ Q.

Here is an example of its use in Lean
-/

theorem pf_PQ: P ∧ Q :=
    and.intro pfP pfQ -- study this carefully!

/-
Ok, let's decode that. We're proposing a theorem.
The proposition we aim to prove is P ∧ Q,
which is really just 0 = 0 ∧ 1 = 1. To produce
a proof of this proposition, we need a proof
of 0 = 0. That is p. We also need a proof of
1 = 1. That is q. If we then apply the rule to
p and q we can expect to get back a proof of
P ∧ Q,=. The Lean checker confirms that we do.

You might be getting the sense that these
inference rules are sort of like functions:
They take propositions, proofs, and other
values as arguments and they compute and
return proofs as results. This is exactly
what is happening here. What you're seeing
is how programs like Lean turns propositions
and proofs into objects that can be handled
and transformed by programs! It's like your
ordinary discrete math class on steroids.
-/

/- * PROOF TREES * -/

/-
A key point is that what we just did is to
build a larger proof, pf, from two smaller
ones, p and q. Those proofs in turn we built
using the eq.refl inference rule applied to
the values 0 and 1, respectively. We can now
visualize the whole construction as what we
call a proof tree, or derivation (with more
than one level of rules being applied).

eq.refl 0    eq.refl 1
---------    ---------
  0 = 0        1 = 1
----------------------
     0 = 0 ∧ 1 = 1

We can read such a proof tree either from
the top to the bottom or from the bottom to
the top. We can also translate such a tree
into English. Most mathematicians would just
provide the English language proof, leaving
it to human and social processes to check
that it's good. We now have the benefit of
automated proof checking.

Reading it from the top we might say the
following. By the reflexive property of
equality, we derive that 0 = 0 and that
1 = 1. These lemmas both now been proved
separately, a proof of their conjunction
follows immediately.
QED

Now as one seeking to prove P ∧ Q (the
conclusion), one would work from bottom
to top. The reasoning would go like this:
To prove the conclusion, we need proofs
of ach conjunct. We obtain a proof of the
first, 0 = 0, by noting that this follows
from the reflexive property of equality.
So does a proof of 1 = 1. Now having
proved the individual conjuncts, the truth
of the conjunction follows immediately.
QED.

You can see that the everyday working
mathematician elides many details, such
as the precise principle used to derive
the truth of the conjunction. The lack
of detail in informal proofs makes them
at best nearly impossible for machines
to check. Reading and checking them
really relies on human mathematical
understanding.

Nevertheless, whether you are asked to
write an informal proof or a formal one,
the process of deriving a proof often
involves this kind of "backwards chaining."

When asked to prove a proposition, X, we
generally try to find other propositions,
such as S and T, such that if we had
proofs of S and T, we could then apply
a valid reasoning step (inference rule)
to derive a proof of X.

One thus reduces the problem of proving
X to the sub-problems of proving S and T.
One  then backward-chains this way until
one finally reaches ... axioms, i.e.,
rules with no proofs required above the
line. Axioms are the "base cases" in
the recursive decomposition of the
overall problem: there is no need
to recurse any further once you've
reach the bottom!

To be really precise about it, there
is one more layer in the proof tree:
it's where the Lean type checker gives
us proofs that 0 and 1 are of type nat;
and that's what eq.refl needs as its
input. The type checker in turn gives
us proofs of these type judgments by
means of yet further, but not hidden,
logic reasoning.

 0: nat       1 : nat
---------    ---------
eq.refl 0    eq.refl 1
---------    ---------
  0 = 0        1 = 1
----------------------
     0 = 0 ∧ 1 = 1

QED!

Now you're looking at a depiction
of a mathematically precise proof.
The English just presents such an
object in a more natural language,
but at the cost of a massive loss
of precision and checkability.  The
downside of the formal proof object
is that it's not in the form of a
story; and as you can imagine, in
cases where proofs get massive,
understanding why they are the way
they are can be near impossible.
But that's why we have machines.

In practice, you should really
know how to produce both formal
and informal proofs. Better yet,
you might someday try some tools
that produce formal proofs for
you. (Such a tool is Dafny. We
will perhaps see it later.)
-/

/-
EXERCISE: What "smaller" propositions
might you want to prove to prove that
5 = 1 + 4 ∧ "Strike" = "S" ++ "trike".
Prove those smaller propositions giving
them whatever names you choose. Then
write the theorem in Lean that proves
the final result using and.intro.
-/

/- * Proving conjunctions with tactics * -/

/-
One can always prove a proposition by
giving an exact proof term, but these
terms are like programs, and they can
get large and complicated. Sometimes,
indeed often, it's easier to develop
them step by step.

A key idea is that we start with the
goal at the bottom of a proof tree, the
goal (proposition) to be proved, and by
working backwards through the tree, we
can identify the smaller propositions
for which we need proofs so as to be
able to apply a final rule to construct
the desired proof.

Consider again the goal of proving the
conjunctive proposition P ∧ Q. If we have
a proof of P and a proof of Q then we
can apply the and.intro rule to build
the proof we need. Well, that breaks the
overall goal into two subgoals, and we
can then solve them recursively (i.e.,
as sub-problems to solve nested within
the solution to the overall problem).

In particular, we can *split* the goal
of proving a conjunction into sub-goals,
one for each conjunct. We then solve
the sub-problems and finally combine
the partial solutions into a final result
using and.intro.

Whether you are using a tool like Lean
or are just writing informal mathematical
proofs, the use of decomposition of a
large problem into pieces, recursively
solving of the parts, and ultimately
the combination of the partial results
into a final result, is essential and
ubiquitous. Indeed, it's a general way
to solve complex problems in almost
any domain. The key is being able to
break a problem into parts that can
be solved independently.
-/

/-
In Lean, we can build proofs this way
using tactic scripts. First we apply
the and.intro rule. Used in a script in
this way, it works backwards! Rather
than combining proofs for P and Q into
a proof of P ∧ Q, it decomposes the
top-level goal of proving P ∧ Q into
two sub-goals: to prove P and to prove
Q. The problem of proving P ∧ Q is
thus decomposed into the problem of
proving premises needed to make the
and.intro rule succeed. We then use
additional tactics too solve each of
the sub-problems, one by one.

Open the Messages view, then put your
cursor at the beginning of each line in
the following script and study how the
tactic state changes as you go.
-/

theorem pf_PQ': P ∧ Q :=
begin
  apply and.intro,
  exact pfP,
  exact pfQ,
end

/-
An English-language rendition of this
proof-constructing procedure would go
like this: "We split the problem of
proving P ∧ Q into the sub-problems,
of proving P and proving Q. This is
a valid step because the and.intro
rule tells us that having such proofs
will suffice to prove the main goal.
A proof of P follows from the reflexive
property of equality, and a proof of Q
also then follows similarly. QED."

Constructing proofs in this way is
very much like writing programs by
first writing the Main routine, having
it call subroutines, and then writing
the subroutines. Here instead we start
with the main goal, P ∧ Q. We apply
an inference rule to break down the
problem of proving the goal into one
or more problems of proving sub-goals,
and once we're done with that, a proof
of the top-level goals follows by the
application of the mai inference rule.

We call this approach to development
of programs, and of proofs, top-down
decomposition. Sometimes a software
will say that she's using "top-down
structured programming" to design her
code. Learning to think this way is
a major step forward for a computer
scientist.
-/

/-
There's another way, of course, to
build a proof, or a program, and that
iss to write the sub-routines first and
then to integrate them into a larger
program by writing code that uses them.
We call such an approach "bottom-up."

You can similarly develop proofs using
a bottom-up approach. You first obtain
the proofs of sub-goals that you'll need
and then you "integrate" (combine) them
by applying some inference rule.

We now introduce a style of Lean proof
scripts that supports such a bottom-up
approach to constructing proofs. Here's
an example where we prove the same
proposition but in a bottom-up manner.
-/

theorem pf_PQ'': P ∧ Q :=
begin
  have X := pfP,
  have Y := pfQ,
  apply and.intro X Y
end

/-
Study very carefully how the tactic state
changes as you advance through the script.
The first tactic we use is "have." It
gives a name to a proof for a sub-goal
that you will then use later on. Here, the
name X is given to a proof of 0 = 0. Once
this tactic has run, you will see the proof state update to a new state in which you
have a proof X of 0 = 0 (before the ⊢),
with P ∧ Q as desired proposition to be
proved. Next we give a name to, and then
subsequently also have a proof of Q. And
finally, we apply the and.intro rule to
these two proofs, which are now available
in the "context" of the proof state (before
the ⊢) to build a proof of the overall goal. Lean checks and accepts that proof thereby
solving the overall problem. QED, as they
say.

This style of proof scripts starts to
look like proofs that a mathematician
might write. This one could be read
like this: "We have a proof of P,
(eq.refl 0). Let's call it X. We
also have a proof of Q, (eq.refl 1).
Let's call it Y. Given proofs X and Y
we now apply the and.intro rule to
X and Y to derive the proof we need.
QED."
-/

/-
Whether you write exact proof objects,
or use top-down or bottom-up scripting
methods, the key point to bear in mind
is that everything ultimately relies
on the inference rules. They are what
is most fundamental. Moving from the
conclusion to the premises of and.intro
is how we decompose the top-level goal,
P ∧ Q, into sub-goals. Moving from the
premises X and Y down through the
and.intro rule to a proof of P ∧ Q is
how we merge bottom-up partial results
into an overall solution. And of course
if we're going to write a proof object
exactly, the expression we need is one
in which eq.refl is used. It's thus very
important that you familiarize yourself
deeply with the introduction (and also
the elimination) inference rules for
predicate logic. Now we move on to the
elimination rules for and (∧).
-/

/- * AND ELIMINATION * -/

/-
Given a proof of P ∧ Q, it's clear that
P should be true individually, and Q too.

The and elimination rules provide us with
these reasoning principles, allowing us to
derive P from P ∧ Q, and Q from P ∧ Q. We
call them and.elim_left and and.elim_right.

{ P Q: Prop } (paq : P ∧ Q)
--------------------------- and.elim_left
         pfP : P

{ P Q: Prop } (paq : P ∧ Q)
--------------------------- and.elim_right
         pfQ : Q
-/

-- recall that t is a proof of P ∧ Q
#check and.elim_left pf_PQ
#check and.elim_right pf_PQ

/-
EXERCISE: Fill in the "sorry" words in
the following incomplete theorems with
explicit proof objects obtained by applying
the and elimination rules to our proof, pf_PQ,
of P ∧ Q.

Note: Sorry says, "please just accept
the theorem as proved: an axiom. It's
dangerous to use sorry  except as a
temporary placeholder for a proof that
you plan fill in later."
-/

theorem pfP' : P := and.elim_left pf_PQ
theorem pfQ' : Q := sorry

/-
Here are two script-based proofs.
-/

theorem pfP'' : P :=
begin
  exact and.elim_left pf_PQ
end

theorem pfP''' : P :=
begin
  have pfP := and.elim_left pf_PQ,
  exact pfP
end

/- * COMMUTATIVITY OF CONJUNCTION *-/

/-
Now we've really got some power tools. We
can prove equalities. From constituent
proofs we can prove conjunctions, and
from proofs of conjunctions we can obtain
proofs of the individual conjuncts. With
these tools in hand we can now start to
state and prove essential mathematical
properties of logical operators, here of
conjunction (and, ∧) .
-/

/- *** Commutativity of ∧ ***-/
/-
Let's first state a claim informally. If
P ∧ Q is true then Q ∧ P must be true
as well. For example, from a proof of
(0 = 0 ∧ 1 = 1) we should be able to
derive a proof of (1 = 1 ∧ 0 = 0).

We already have the propositions, P :=
0 = 0, Q := 1 = 1, and a proof, pf_PQ,
of P ∧ Q (i.e., of 0 = 0 ∧ 1 =1 ). Now
let's give a name to the proposition
that (1 = 1 ∧ 0 = 0).

-/

def Q_and_P : Prop := Q ∧ P -- reverse!

/-
How can we generate a proof of Q_and_P?
Let's think in a top-down manner. Start
with the goal, Q ∧ P, and ask how it can
be proved. The only way we can prove it
at present is by applying and.intro to a
proof of Q and a proof of P (in this new
and reversed order!).

So the form of a proof simply has to
be (and.intro _ _), where the first _
is a proof of Q and the second _ is a
proof of P.

If all we start with is a proof, pf_PQ,
of P ∧ Q, how can get the proofs, pfP of
P and pfQ of Q, that we need?

Easy! Apply the and.elimination rules
to get first a proof of Q then a proof
of P; then use those proofs as arguments
to and.intro, the first being the proof
of Q and the second, the proof of P.
-/

theorem pfQaP : Q ∧ P :=
  and.intro
    (and.elim_right pf_PQ)
    (and.elim_left pf_PQ)

/-
EXERCISE: Write a top-down proof script
to prove the same proposition, calling
the result pfQaP'.

EXERCISE: Write a bottom-up proof script
to prove the same proposition, calling
the result pfQaP''.
-/

theorem pfQaP' : Q ∧ P :=
begin
  apply and.intro,
  exact (and.elim_right pf_PQ),
  exact (and.elim_left pf_PQ),
end

theorem pfQaP'' : Q ∧ P :=
begin
  have pfQ := (and.elim_right pf_PQ),
  have pfP := (and.elim_left pf_PQ),
  apply and.intro pfQ pfP
end

/-
The general inference rule, which we
will call and.commutes, says that if
P and Q are *any* propositions, and
we have a proof, paq, of P ∧ Q, then
we can obtain a proof, qap, of Q ∧ P.
We could write the rule like this
(the arguments P and Q are implicit
and are inferred from paq).

{ P Q: Prop }, paq : P ∧ Q
-------------------------- and_commutes
        qap: Q ∧ P

We can now even see how we might
implement this rule ourselves --
literally as a program! We'll call
it "and_commutes".
-/

def and_commutes { P Q: Prop } (paq: P ∧ Q) : Q ∧ P :=
  and.intro (and.elim_right paq) (and.elim_left paq)

#check and_commutes

/-
In this program we use the same curly
brace notation as introduced informally
above to indicate parameters that are to
be inferred from context. That is, we can
apply this function to a proof of P ∧ Q,
without explicitly giving P and Q as
parameters, to obtain a proof of Q ∧ P.
Does it work?
-/

theorem qap : Q ∧ P := (and_commutes pf_PQ)

/-
Holy cow, it works! We applied our new
*proof converter* to convert a proof
of 0 = 0 ∧ 1 = 1 into a proof of 1 = 1 ∧
0 = 0. But we've written our converter
program in a way that will work for any
P and Q are. We have in effect proven
that conjunction is commutative: that
for any propositions, P and Q, if P
∧ Q is true, then we can derive that
Q ∧ P is, too. Now things are getting
interesting.
-/

/-
EXERCISE: From a proof of 1 = 1 ∧ tt = tt,
use and_commutes to prove tt = tt ∧ 1 = 1.
-/

/-
Having now proved that "and commutes,"
as mathematicians would say, we can now
use this principle in all future reasoning.
Indeed in the work of everyday mathematics,
if one had proved P ∧ Q and needed a proof
of Q ∧ P, it'd be a simple matter of saying
"Q ∧ P follows from the commutativity of
conjunction." Many mathematicians would
just say, "Q ∧ P follows immediately,"
leaving it to the reader to know that
the commutativity property of ∧ is the
real justification for the conclusion.
-/


import data.nat.prime
import algebra.big_operators
import tactic

/- TEXT:
.. _section_induction_and_recursion:

Induction and Recursion
-----------------------

The set of natural numbers :math:`\mathbb{N} = \{ 0, 1, 2, \ldots \}`
is not only fundamentally important in its own right,
but also a plays a central role in the construction of new mathematical objects.
Lean's foundation allows us to declare *inductive types*,
which are types generated inductively by a given list of
*constructors*.
In Lean, the natural numbers are declared as follows.
OMIT: -/
namespace hidden
-- QUOTE:
inductive nat
| zero : nat
| succ (n : nat) : nat
-- QUOTE.
end hidden

/- TEXT:
You can find this in the library by writting ``#check nat`` and
then using ``ctrl-click`` on the identifier ``nat``.
The command specifies that ``nat`` is the datatype generated
freely and inductively by the two constructors ``zero : nat`` and
``succ : nat → nat``.
Of course, the library introduces notation ``ℕ`` and ``0`` for
``nat`` and ``zero`` respectively. (Numerals are translated to binary
representations, but we don't have to worry about the details of that now.)

What "freely" means for the working mathematician is that the type
``nat`` has an element ``zero`` and an injective successor function
``succ`` whose image does not include ``zero``.
EXAMPLES: -/
-- QUOTE:
example (n : nat) : n.succ ≠ nat.zero := nat.succ_ne_zero n

example (m n : nat) (h : m.succ = n.succ) : m = n := nat.succ.inj h
-- QUOTE.

/- TEXT:
What the word "inductively" means for the working mathematician is that
the natural numbers comes with a principle of proof by induction
and a principle of definition by recursion.
This section will show you how to use these.

Here is an example of a recursive definition of the factorial
function.
BOTH: -/
-- QUOTE:
def fac : ℕ → ℕ
| 0       := 1
| (n + 1) := (n + 1) * fac n
-- QUOTE.

/- TEXT:
The syntax takes some getting used to.
Notice that there is no ``:=`` on the first line.
The next two lines provide the base case and inductive step
for a recursive definition.
These equations hold definitionally, but they can also
be used manually by giving the name ``fac`` to ``simp`` or ``rw``.
EXAMPLES: -/
-- QUOTE:
example : fac 0 = 1 := rfl
example : fac 0 = 1 := by rw fac
example : fac 0 = 1 := by simp [fac]

example (n : ℕ) : fac (n + 1) = (n + 1) * fac n := rfl
example (n : ℕ) : fac (n + 1) = (n + 1) * fac n := by rw fac
example (n : ℕ) : fac (n + 1) = (n + 1) * fac n := by simp [fac]
-- QUOTE.

/- TEXT:
The factorial function is actually already defined in mathlib as
``nat.factorial``. Once again, you can jump to it by typing
``#check nat.factorial`` and using ``ctrl-click.``
For illustrative purposes, we will continue using ``fac`` in the examples.
The annotation ``@[simp]`` before the definition
of ``nat.factorial`` specifies that
the defining equation should be added to the database of identities
that the simplifier uses by default.

The principle of induction says that we can prove a general statement
about the natural numbers by proving that the statement holds of 0
and that whenever it holds of a natural number :math:`n`,
it also holds of :math:`n + 1`.
The line ``induction n with n ih`` in the proof
below therefore results in two goals:
in the first we need to prove ``0 < fac 0``,
and in the second we have the added assumption ``ih : 0 < fac n``
and a required to prove ``0 < fac (n + 1)``.
The phrase ``with n ih`` serves to name the variable and
the assumption for the inductive hypothesis,
and you can choose whatever names you want for them.
EXAMPLES: -/
-- QUOTE:
theorem fac_pos (n : ℕ) : 0 < fac n :=
begin
  induction n with n ih,
  { rw fac, exact zero_lt_one },
  rw fac,
  exact mul_pos n.succ_pos ih,
end
-- QUOTE.

/- TEXT:
The ``induction`` tactic is smart enough to include hypotheses
that depend on the induction variable as part of the
induction hypothesis.
Step through the next example to see what is going on.
EXAMPLES: -/
-- QUOTE:
theorem dvd_fac {i n : ℕ} (ipos : 0 < i) (ile : i ≤ n) : i ∣ fac n :=
begin
  induction n with n ih,
  { exact absurd ipos (not_lt_of_ge ile) },
  rw fac,
  cases nat.of_le_succ ile with h h,
  { apply dvd_mul_of_dvd_right (ih h) },
  rw h,
  apply dvd_mul_right
end
-- QUOTE.

/- TEXT:
The following example provides a crude lower bound for the factorial
function.
It turns out to be easier to start with a proof by cases,
so that the remainder of the proof starts with the case
:math:`n = 1`.
See if you can complete the argument with a proof by induction.
BOTH: -/
-- QUOTE:
theorem pow_two_le_fac (n : ℕ) : 2^(n-1) ≤ fac n :=
begin
  cases n with n,
  { simp [fac] },
/- EXAMPLES:
  sorry
SOLUTIONS: -/
  induction n with n ih,
  { simp [fac] },
  simp at *,
  rw [pow_succ, fac],
  apply nat.mul_le_mul _ ih,
  repeat { apply nat.succ_le_succ },
  apply zero_le
-- BOTH:
end
-- QUOTE.

/- TEXT:
Induction is often used to prove identities involving finite sums and
products.
Mathlib defines the expressions ``finset.sum s f`` where
``s : finset α`` if a finite set of elements of the type ``α`` and
``f`` is a function defined on ``α``.
The codomain of ``f`` can be any type that supports a commutative,
associative addition operation with a zero element.
If you import ``algebra.big_operators`` and issue the command
``open_locale big_operators``, you can use the more suggestive notation
``∑ x in s, f x``. Of course, there is are an analogous operation and
notation for finite products.

We will talk about the ``finset`` type and the operations
it supports in the next section, and again in a later chapter.
For now, we will only make use
of ``finset.range n``, which is the finite set of natural numbers
less than ``n``.
BOTH: -/
section

-- QUOTE:
variables {α : Type*} (s : finset ℕ) (f : ℕ → ℕ) (n : ℕ)

-- EXAMPLES:
#check finset.sum s f
#check finset.prod s f

-- BOTH:
open_locale big_operators
open finset

-- EXAMPLES:
example : s.sum f = ∑ x in s, f x := rfl
example : s.prod f = ∏ x in s, f x := rfl

example : (range n).sum f = ∑ x in range n, f x := rfl
example : (range n).prod f = ∏ x in range n, f x := rfl
-- QUOTE.

/- TEXT:
The facts ``finset.sum_range_zero`` and ``finset.sum_range_succ``
provide a recursive description summation up to :math:`n`,
and similarly for products.
EXAMPLES: -/
-- QUOTE:
example (f : ℕ → ℕ) : ∑ x in range 0, f x = 0 :=
finset.sum_range_zero f

example (f : ℕ → ℕ) (n : ℕ): ∑ x in range n.succ, f x = (∑ x in range n, f x) + f n :=
finset.sum_range_succ f n

example (f : ℕ → ℕ) : ∏ x in range 0, f x = 1 :=
finset.prod_range_zero f

example (f : ℕ → ℕ) (n : ℕ): ∏ x in range n.succ, f x = (∏ x in range n, f x) * f n :=
finset.prod_range_succ f n
-- QUOTE.

/- TEXT:
The first identity in each pair holds definitionally, which is to say,
you can replace the proofs by ``rfl``.

The following expresses the factorial function that we defined as a product.
EXAMPLES: -/
-- QUOTE:

example (n : ℕ) : fac n = ∏ i in range n, (i + 1) :=
begin
  induction n with n ih,
  { simp [fac] },
  simp [fac, ih, prod_range_succ, mul_comm]
end
-- QUOTE.

/- TEXT:
The fact that we include ``mul_comm`` as a simplification rule deserves
comment.
It should seem dangerous to simplify with the identity ``x * y = y * x``,
which would ordinarily loop indefinitely.
Lean's simplifier is smart enough to recognize that, and applies the rule
only in the case where the resulting term has a smaller value in some
fixed but arbitrary ordering of the terms.
The following example shows that simplifying using the three rules
``mul_assoc``, ``mul_comm``, and ``mul_left_comm``
manages to identify products that are the same up to the
placement of parentheses and ordering of variables.
EXAMPLES: -/
-- QUOTE:
example (a b c d e f : ℕ) : a * ((b * c) * f * (d * e)) = d * (a * f * e) * (c * b) :=
by simp [mul_assoc, mul_comm, mul_left_comm]
-- QUOTE.

/- TEXT:
Roughly, the rules work by pushing parentheses to the right
and then re-ordering the expressions on both sides until they
both follow the same canonical order. Simplifying with these
rules, and the corresponding rules for addition, is a handy trick.

Returning to summation identities, we suggest stepping through the following proof
that the sum of the natual numbers up to an including :math:`n` is
:math:`n (n + 1) / 2`.
The first step of the proof clears the denominator.
This is generally useful when formalizing identities,
because calculations with division generally have side conditions.
(It is similarly useful to avoid using subtraction on the natural numbers when possible.)
EXAMPLES: -/
-- QUOTE:
theorem sum_id (n : ℕ) : ∑ i in range (n + 1), i = n * (n + 1) / 2 :=
begin
  symmetry, apply nat.div_eq_of_eq_mul_right (by norm_num : 0 < 2),
  induction n with n ih,
  { simp },
  rw [finset.sum_range_succ, mul_add 2, ←ih, nat.succ_eq_add_one],
  ring
end
-- QUOTE.

/- TEXT:
We encourage you to prove the analogous identity for sums of squares,
and other identities you can find on the web.
BOTH: -/
-- QUOTE:
theorem sum_sqr (n : ℕ) : ∑ i in range (n + 1), i^2 = n * (n + 1) * (2 *n + 1) / 6 :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
begin
  symmetry, apply nat.div_eq_of_eq_mul_right (by norm_num : 0 < 6),
  induction n with n ih,
  { simp },
  rw [finset.sum_range_succ, mul_add 6, ←ih, nat.succ_eq_add_one],
  ring
end
-- QUOTE.
-- BOTH:

end

/- TEXT:
In Lean's core library, addition and multiplication are themselves defined
using recursive definitions,
and their fundamental properties are established using induction.
If you like thinking about foundational topics like that,
you might enjoy working through proofs
of the commutativity and associativity of multiplication and addition
and the distributivity of multiplication over addition.
You can do this on a copy of the natural numbers
following the outline below.
Notice that we can use the ``induction`` tactic with ``my_nat``;
Lean is smart enough to know to
use the relevant induction principle (which is, of course,
the same as that for ``nat``).

We start you off with the commutativity of addition.
A good rule of thumb is that because addition and multiplication
are defined by recursion on the second argument,
it is generally advantageous to do proofs by induction on a variable
that occurs in that position.
It is a bit tricky to decide which variable to use in the proof
of associativity.

It can be confusing to write things without the usual notation
for zero, one, addition, and multiplication.
We will learn how to define such notation later.
Working in the namespace ``my_nat`` means that we can write
``zero`` and ``succ`` rather than ``my_nat.zero`` and ``my_nat.succ``,
and that these interpretations of the names take precedence over
others.
Outside the namespace, the full name of the ``add`` defined below,
for example, is ``my_nat.add``.

If you find that you *really* enjoy this sort of thing, try defining
truncated subtraction and exponentiation and proving some of their
properties as well.
Remember that truncated subtraction cuts off at zero.
To define that, it is useful to define a predecessor function, ``pred``,
that subtracts one from any nonzero number and fixes zero.
The function ``pred`` can be defined by a simple instance of recursion.
BOTH: -/
-- QUOTE:
inductive my_nat
| zero : my_nat
| succ : my_nat → my_nat

namespace my_nat

def add : my_nat → my_nat → my_nat
| x zero     := x
| x (succ y) := succ (add x y)

def mul : my_nat → my_nat → my_nat
| x zero     := zero
| x (succ y) := add (mul x y) x

theorem zero_add (n : my_nat) : add zero n = n :=
begin
  induction n with n ih,
  { refl },
  rw [add, ih]
end

theorem succ_add (m n : my_nat) : add (succ m) n = succ (add m n) :=
begin
  induction n with n ih,
  { refl },
  rw [add, ih],
  refl
end

theorem add_comm (m n : my_nat) : add m n = add n m :=
begin
  induction n with n ih,
  { rw zero_add, refl },
  rw [add, succ_add, ih]
end

theorem add_assoc (m n k : my_nat) : add (add m n) k = add m (add n k) :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
begin
  induction k with k ih,
  { refl },
  rw [add, ih],
  refl
end
-- BOTH:

theorem mul_add  (m n k : my_nat) : mul m (add n k) = add (mul m n) (mul m k) :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
begin
  induction k with k ih,
  { refl },
  rw [add, mul, mul, ih, add_assoc]
end
-- BOTH:

theorem zero_mul (n : my_nat) : mul zero n = zero :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
begin
  induction n with n ih,
  { refl },
  rw [mul, ih],
  refl
end
-- BOTH:

theorem succ_mul (m n : my_nat) : mul (succ m) n = add (mul m n) n :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
begin
  induction n with n ih,
  { refl },
  rw [mul, mul, ih, add_assoc, add_assoc, add_comm n, succ_add],
  refl
end
-- BOTH:

theorem mul_comm (m n : my_nat) : mul m n = mul n m :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
begin
  induction n with n ih,
  { rw [zero_mul], refl },
  rw [mul, ih, succ_mul]
end
-- BOTH:

end my_nat
-- QUOTE.


-- ---------------------------------------------------------------------
-- Ejercicio. Realizar las siguientes acciones:
-- 1. Importar la librería data.nat.prime data.nat.parity
-- 2. Abrir el espacio de nombres nat
-- 3. Declarar s y t como variables sobre conjuntos de números naturales.
-- 4. Declarar el hecho (ssubt : s ⊆ t)
-- 5, Añadir ssubt como hipótesis de la teoría.
-- ----------------------------------------------------------------------

import data.nat.prime data.nat.parity  -- 1
open nat                               -- 2
variables (s t : set ℕ)                -- 3
variables (ssubt : s ⊆ t)              -- 4
include ssubt                          -- 5

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si
--    ∀ x ∈ t, ¬ even x
--    ∀ x ∈ t, prime x
-- entonces
--    ∀ x ∈ s, ¬ even x ∧ prime x
-- ----------------------------------------------------------------------

example
  (h₀ : ∀ x ∈ t, ¬ even x)
  (h₁ : ∀ x ∈ t, prime x)
  : ∀ x ∈ s, ¬ even x ∧ prime x :=
begin
  intros x xs,
  split,
  { apply h₀ x (ssubt xs) },
  { apply h₁ x (ssubt xs) },
end

-- Prueba
-- ======

/-
s t : set ℕ,
ssubt : s ⊆ t,
h₀ : ∀ (x : ℕ), x ∈ t → ¬x.even,
h₁ : ∀ (x : ℕ), x ∈ t → x.prime
⊢ ∀ (x : ℕ), x ∈ s → ¬x.even ∧ x.prime
  >> intros x xs,
x : ℕ,
xs : x ∈ s
⊢ ¬x.even ∧ x.prime
  >> split,
| ⊢ ¬x.even
|   >> { apply h₀ x (ssubt xs) },
⊢ x.prime
  >> { apply h₁ x (ssubt xs) },
no goals
-/

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si
--    ∃ x ∈ s, ¬ even x ∧ prime x
-- entonces
--   ∃ x ∈ t, prime x
-- ----------------------------------------------------------------------

example
  (h : ∃ x ∈ s, ¬ even x ∧ prime x)
  : ∃ x ∈ t, prime x :=
begin
  rcases h with ⟨x, xs, _, px⟩,
  use [x, ssubt xs, px],
end

-- Prueba
-- ======

/-
s t : set ℕ,
ssubt : s ⊆ t,
h : ∃ (x : ℕ) (H : x ∈ s), ¬x.even ∧ x.prime
⊢ ∃ (x : ℕ) (H : x ∈ t), x.prime
  >> rcases h with ⟨x, xs, _, px⟩,
x : ℕ,
xs : x ∈ s,
h_h_h_left : ¬x.even,
px : x.prime
⊢ ∃ (x : ℕ) (H : x ∈ t), x.prime
  >> use [x, ssubt xs, px],
no goals
-/


import data.real.basic

def S := {x : real | x*x <= 2}

-- 1ª demostración
example : (0 : ℝ) ∈ S :=
begin
  dsimp [S],
  norm_num,
end

-- 2ª demostración
example : (0 : ℝ) ∈ S :=
begin
  change (0 : ℝ) * 0 ≤ 2,
  norm_num,
end

-- 3ª demostración
example : (0 : ℝ) ∈ S :=
begin
  unfold S,
  rw set.mem_set_of_eq,
  norm_num,
end


/-
Copyright (c) 2022 Jiale Miao. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jiale Miao, Kevin Buzzard
-/

import analysis.inner_product_space.projection

/-!
# Gram-Schmidt Orthogonalization and Orthonormalization

In this file we introduce Gram-Schmidt Orthogonalization and Orthonormalization.

The Gram-Schmidt process takes a set of vectors as input
and outputs a set of orthogonal vectors which have the same span.

## Main results

- `gram_schmidt` : the Gram-Schmidt process
- `gram_schmidt_orthogonal` :
  `gram_schmidt` produces an orthogonal system of vectors.
- `span_gram_schmidt` :
  `gram_schmidt` preserves span of vectors.
- `gram_schmidt_ne_zero` :
  If the input of the first `n + 1` vectors of `gram_schmidt` are linearly independent,
  then the output of the first `n + 1` vectors are non-zero.
- `gram_schmidt_normed` :
  the normalized `gram_schmidt` (i.e each vector in `gram_schmidt_normed` has unit length.)
- `gram_schmidt_orthornormal` :
  `gram_schmidt_normed` produces an orthornormal system of vectors.

## TODO
  Construct a version with an orthonormal basis from Gram-Schmidt process.
-/

open_locale big_operators

variables (𝕜 : Type*) {E : Type*} [is_R_or_C 𝕜] [inner_product_space 𝕜 E]

local notation `⟪`x`, `y`⟫` := @inner 𝕜 _ _ x y

/-- The Gram-Schmidt process takes a set of vectors as input
and outputs a set of orthogonal vectors which have the same span. -/
noncomputable def gram_schmidt (f : ℕ → E) : ℕ → E
| n := f n - ∑ i : fin n, orthogonal_projection (𝕜 ∙ gram_schmidt i) (f n)
using_well_founded {dec_tac := `[exact i.prop]}

/-- `gram_schmidt_def` turns the sum over `fin n` into a sum over `ℕ`. -/
lemma gram_schmidt_def (f : ℕ → E) (n : ℕ) :
  gram_schmidt 𝕜 f n = f n - ∑ i in finset.range n,
    orthogonal_projection (𝕜 ∙ gram_schmidt 𝕜 f i) (f n) :=
begin
  rw gram_schmidt,
  congr' 1,
  exact fin.sum_univ_eq_sum_range (λ i,
    (orthogonal_projection (𝕜 ∙ gram_schmidt 𝕜 f i) (f n) : E)) n,
end

lemma gram_schmidt_def' (f : ℕ → E) (n : ℕ):
  f n = gram_schmidt 𝕜 f n + ∑ i in finset.range n,
    orthogonal_projection (𝕜 ∙ gram_schmidt 𝕜 f i) (f n) :=
by simp only [gram_schmidt_def, sub_add_cancel]

@[simp] lemma gram_schmidt_zero (f : ℕ → E) :
  gram_schmidt 𝕜 f 0 = f 0 :=
by simp only [gram_schmidt, fintype.univ_of_is_empty, finset.sum_empty, sub_zero]

/-- **Gram-Schmidt Orthogonalisation**:
`gram_schmidt` produces an orthogonal system of vectors. -/
theorem gram_schmidt_orthogonal (f : ℕ → E) {a b : ℕ} (h₀ : a ≠ b) :
  ⟪gram_schmidt 𝕜 f a, gram_schmidt 𝕜 f b⟫ = 0 :=
begin
  suffices : ∀ a b : ℕ, a < b → ⟪gram_schmidt 𝕜 f a, gram_schmidt 𝕜 f b⟫ = 0,
  { cases h₀.lt_or_lt with ha hb,
    { exact this _ _ ha, },
    { rw inner_eq_zero_sym,
      exact this _ _ hb, }, },
  clear h₀ a b,
  intros a b h₀,
  induction b using nat.strong_induction_on with b ih generalizing a,
  simp only [gram_schmidt_def 𝕜 f b, inner_sub_right, inner_sum,
    orthogonal_projection_singleton, inner_smul_right],
  rw finset.sum_eq_single_of_mem a (finset.mem_range.mpr h₀),
  { by_cases h : gram_schmidt 𝕜 f a = 0,
    { simp only [h, inner_zero_left, zero_div, zero_mul, sub_zero], },
    { rw [← inner_self_eq_norm_sq_to_K, div_mul_cancel, sub_self],
      rwa [ne.def, inner_self_eq_zero], }, },
  simp_intros i hi hia only [finset.mem_range],
  simp only [mul_eq_zero, div_eq_zero_iff, inner_self_eq_zero],
  right,
  cases hia.lt_or_lt with hia₁ hia₂,
  { rw inner_eq_zero_sym,
    exact ih a h₀ i hia₁, },
  { exact ih i hi a hia₂, },
end

/-- This is another version of `gram_schmidt_orthogonal` using `pairwise` instead. -/
theorem gram_schmidt_pairwise_orthogonal (f : ℕ → E) :
  pairwise (λ a b, ⟪gram_schmidt 𝕜 f a, gram_schmidt 𝕜 f b⟫ = 0) :=
@gram_schmidt_orthogonal 𝕜 _ _ _ f

open submodule set order

/-- `gram_schmidt` preserves span of vectors. -/
lemma span_gram_schmidt (f : ℕ → E) (c : ℕ) :
  span 𝕜 (gram_schmidt 𝕜 f '' Iic c) = span 𝕜 (f '' Iic c) :=
begin
  induction c with c hc,
  { simp only [Iic, gram_schmidt_zero, le_zero_iff, set_of_eq_eq_singleton, image_singleton], },
  have h₀ : ∀ b, b ∈ finset.range c.succ → gram_schmidt 𝕜 f b ∈ span 𝕜 (f '' Iic c),
  { simp_intros b hb only [finset.mem_range, nat.succ_eq_add_one],
    rw ← hc,
    refine subset_span _,
    simp only [mem_image, mem_Iic],
    refine ⟨b, by linarith, by refl⟩, },
  rw [← nat.succ_eq_succ, Iic_succ],
  simp only [span_insert, image_insert_eq, hc],
  apply le_antisymm,
  { simp only [nat.succ_eq_succ,gram_schmidt_def 𝕜 f c.succ, orthogonal_projection_singleton,
      sup_le_iff, span_singleton_le_iff_mem, le_sup_right, and_true],
    apply submodule.sub_mem _ _ _,
    { exact mem_sup_left (mem_span_singleton_self (f c.succ)), },
    { exact submodule.sum_mem _ (λ b hb, mem_sup_right (smul_mem _ _ (h₀ b hb))), }, },
  { rw [nat.succ_eq_succ, gram_schmidt_def' 𝕜 f c.succ],
    simp only [orthogonal_projection_singleton,
      sup_le_iff, span_singleton_le_iff_mem, le_sup_right, and_true],
    apply submodule.add_mem _ _ _,
    { exact mem_sup_left (mem_span_singleton_self (gram_schmidt 𝕜 f c.succ)), },
    { exact submodule.sum_mem _ (λ b hb, mem_sup_right (smul_mem _ _ (h₀ b hb))), }, },
end

/-- If the input of the first `n + 1` vectors of `gram_schmidt` are linearly independent,
then the output of the first `n + 1` vectors are non-zero. -/
lemma gram_schmidt_ne_zero (f : ℕ → E) (n : ℕ)
  (h₀ : linear_independent 𝕜 (f ∘ (coe : fin n.succ → ℕ))) :
    gram_schmidt 𝕜 f n ≠ 0 :=
begin
  induction n with n hn,
  { intro h,
    simp only [gram_schmidt_zero, ne.def] at h,
    exact linear_independent.ne_zero 0 h₀ (by simp only [function.comp_app, fin.coe_zero, h]), },
  { by_contra h₁,
    rw nat.succ_eq_add_one at hn h₀ h₁,
    have h₂ := gram_schmidt_def' 𝕜 f n.succ,
    simp only [nat.succ_eq_add_one, h₁, orthogonal_projection_singleton, zero_add] at h₂,
    have h₃ : f (n + 1) ∈ span 𝕜 (f '' Iic n),
    { rw [h₂, ← span_gram_schmidt 𝕜 f n],
      apply submodule.sum_mem _ _,
      simp_intros a ha only [finset.mem_range],
      apply submodule.smul_mem _ _ _,
      refine subset_span _,
      simp only [mem_image, mem_Iic],
      exact ⟨a, by linarith, by refl⟩, },
    change linear_independent 𝕜 (f ∘ (coe : fin (n + 2) → ℕ)) at h₀,
    have h₄ : ((n + 1) : fin (n + 2)) ∉ (coe : fin (n + 2) → ℕ) ⁻¹' (Iic n),
    { simp only [mem_preimage, mem_Iic, not_le],
      norm_cast,
      rw fin.coe_coe_of_lt;
      linarith, },
    apply linear_independent.not_mem_span_image h₀ h₄,
    rw [image_comp, image_preimage_eq_inter_range],
    simp only [function.comp_app, subtype.range_coe_subtype],
    convert h₃,
    { norm_cast,
      refine fin.coe_coe_of_lt (by linarith), },
    { simp only [inter_eq_left_iff_subset, Iic, set_of_subset_set_of],
      exact (λ a ha, by linarith), }, },
end

/-- If the input of `gram_schmidt` is linearly independent, then the output is non-zero. -/
lemma gram_schmidt_ne_zero' (f : ℕ → E) (h₀ : linear_independent 𝕜 f) (n : ℕ) :
  gram_schmidt 𝕜 f n ≠ 0 :=
gram_schmidt_ne_zero 𝕜 f n (linear_independent.comp h₀ _ (fin.coe_injective))

/-- the normalized `gram_schmidt`
(i.e each vector in `gram_schmidt_normed` has unit length.) -/
noncomputable def gram_schmidt_normed (f : ℕ → E) (n : ℕ) : E :=
(∥gram_schmidt 𝕜 f n∥ : 𝕜)⁻¹ • (gram_schmidt 𝕜 f n)

lemma gram_schmidt_normed_unit_length (f : ℕ → E) (n : ℕ)
  (h₀ : linear_independent 𝕜 (f ∘ (coe : fin n.succ → ℕ))) :
    ∥gram_schmidt_normed 𝕜 f n∥ = 1 :=
by simp only [gram_schmidt_ne_zero 𝕜 f n h₀,
  gram_schmidt_normed, norm_smul_inv_norm, ne.def, not_false_iff]

lemma gram_schmidt_normed_unit_length' (f : ℕ → E) (n : ℕ)
  (h₀ : linear_independent 𝕜 f) :
    ∥gram_schmidt_normed 𝕜 f n∥ = 1 :=
by simp only [gram_schmidt_ne_zero' 𝕜 f h₀,
  gram_schmidt_normed, norm_smul_inv_norm, ne.def, not_false_iff]

/-- **Gram-Schmidt Orthonormalization**:
`gram_schmidt_normed` produces an orthornormal system of vectors. -/
theorem gram_schmidt_orthonormal (f : ℕ → E) (h₀ : linear_independent 𝕜 f) :
  orthonormal 𝕜 (gram_schmidt_normed 𝕜 f) :=
begin
  unfold orthonormal,
  split,
  { simp only [gram_schmidt_normed_unit_length', h₀, forall_const], },
  { intros i j hij,
    simp only [gram_schmidt_normed, inner_smul_left, inner_smul_right, is_R_or_C.conj_inv,
      is_R_or_C.conj_of_real, mul_eq_zero, inv_eq_zero, is_R_or_C.of_real_eq_zero, norm_eq_zero],
    repeat { right },
    exact gram_schmidt_orthogonal 𝕜 f hij, },
end