/-
Copyright (c) 2020 Alena Gusakov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alena Gusakov, Arthur Paulino, Kyle Miller
-/
import combinatorics.simple_graph.degree_sum
import combinatorics.simple_graph.subgraph

/-!
# Matchings

A *matching* for a simple graph is a set of disjoint pairs of adjacent vertices, and the set of all
the vertices in a matching is called its *support* (and sometimes the vertices in the support are
said to be *saturated* by the matching). A *perfect matching* is a matching whose support contains
every vertex of the graph.

In this module, we represent a matching as a subgraph whose vertices are each incident to at most
one edge, and the edges of the subgraph represent the paired vertices.

## Main definitions

* `simple_graph.subgraph.is_matching`: `M.is_matching` means that `M` is a matching of its
  underlying graph.
  denoted `M.is_matching`.

* `simple_graph.subgraph.is_perfect_matching` defines when a subgraph `M` of a simple graph is a
  perfect matching, denoted `M.is_perfect_matching`.

## TODO

* Define an `other` function and prove useful results about it (https://leanprover.zulipchat.com/#narrow/stream/252551-graph-theory/topic/matchings/near/266205863)

* Provide a bicoloring for matchings (https://leanprover.zulipchat.com/#narrow/stream/252551-graph-theory/topic/matchings/near/265495120)

* Tutte's Theorem

* Hall's Marriage Theorem (see combinatorics.hall)
-/

universe u

namespace simple_graph
variables {V : Type u} {G : simple_graph V} (M : subgraph G)

namespace subgraph

/--
The subgraph `M` of `G` is a matching if every vertex of `M` is incident to exactly one edge in `M`.
We say that the vertices in `M.support` are *matched* or *saturated*.
-/
def is_matching : Prop := ∀ ⦃v⦄, v ∈ M.verts → ∃! w, M.adj v w

/-- Given a vertex, returns the unique edge of the matching it is incident to. -/
noncomputable def is_matching.to_edge {M : subgraph G} (h : M.is_matching)
  (v : M.verts) : M.edge_set :=
⟨⟦(v, (h v.property).some)⟧, (h v.property).some_spec.1⟩

lemma is_matching.to_edge_eq_of_adj {M : subgraph G} (h : M.is_matching) {v w : V}
  (hv : v ∈ M.verts) (hvw : M.adj v w) : h.to_edge ⟨v, hv⟩ = ⟨⟦(v, w)⟧, hvw⟩ :=
begin
  simp only [is_matching.to_edge, subtype.mk_eq_mk],
  congr,
  exact ((h (M.edge_vert hvw)).some_spec.2 w hvw).symm,
end

lemma is_matching.to_edge.surjective {M : subgraph G} (h : M.is_matching) :
  function.surjective h.to_edge :=
begin
  rintro ⟨e, he⟩,
  refine sym2.ind (λ x y he, _) e he,
  exact ⟨⟨x, M.edge_vert he⟩, h.to_edge_eq_of_adj _ he⟩,
end

lemma is_matching.to_edge_eq_to_edge_of_adj {M : subgraph G} {v w : V}
  (h : M.is_matching) (hv : v ∈ M.verts) (hw : w ∈ M.verts) (ha : M.adj v w) :
  h.to_edge ⟨v, hv⟩ = h.to_edge ⟨w, hw⟩ :=
by rw [h.to_edge_eq_of_adj hv ha, h.to_edge_eq_of_adj hw (M.symm ha),
       subtype.mk_eq_mk, sym2.eq_swap]

/--
The subgraph `M` of `G` is a perfect matching on `G` if it's a matching and every vertex `G` is
matched.
-/
def is_perfect_matching : Prop := M.is_matching ∧ M.is_spanning

lemma is_matching.support_eq_verts {M : subgraph G} (h : M.is_matching) : M.support = M.verts :=
begin
  refine M.support_subset_verts.antisymm (λ v hv, _),
  obtain ⟨w, hvw, -⟩ := h hv,
  exact ⟨_, hvw⟩,
end

lemma is_matching_iff_forall_degree {M : subgraph G} [Π (v : V), fintype (M.neighbor_set v)] :
  M.is_matching ↔ ∀ (v : V), v ∈ M.verts → M.degree v = 1 :=
by simpa [degree_eq_one_iff_unique_adj]

lemma is_perfect_matching_iff : M.is_perfect_matching ↔ ∀ v, ∃! w, M.adj v w :=
begin
  refine ⟨_, λ hm, ⟨λ v hv, hm v, λ v, _⟩⟩,
  { rintro ⟨hm, hs⟩ v,
    exact hm (hs v) },
  { obtain ⟨w, hw, -⟩ := hm v,
    exact M.edge_vert hw }
end

lemma is_perfect_matching_iff_forall_degree {M : subgraph G} [Π v, fintype (M.neighbor_set v)] :
  M.is_perfect_matching ↔ ∀ v, M.degree v = 1 :=
by simp [degree_eq_one_iff_unique_adj, is_perfect_matching_iff]

lemma is_perfect_matching.even_card {M : subgraph G} [fintype V] (h : M.is_perfect_matching) :
  even (fintype.card V) :=
by { classical, simpa [h.2.card_verts] using is_matching.even_card h.1 }

end subgraph

end simple_graph


section Other_Recursive_Data_Types
namespace Hidden

inductive List (α : Type u) where
  | nil   : List α
  | cons  : α → List α → List α

namespace List

def append (as bs : List α) : List α :=
  match as with
  | nil       => bs
  | cons a as => cons a (append as bs)

theorem nil_append (as : List α) : append nil as = as := by
  rfl

theorem cons_append (a : α) (as bs : List α)
  : append (cons a as) bs = cons a (append as bs) := by
  rfl

theorem append_nil (as : List α) : append as nil = as :=
  List.recOn (motive := fun as => append as nil = as)
    as
    (show append nil nil = nil from rfl)
    (fun (a : α) (as : List α) (ih : append as nil = as) =>
      show append (cons a as) nil = cons a as from
      calc
        append (cons a as) nil = cons a (append as nil) := by rw [cons_append]
        _ = cons a as := by rw [ih]
    )

-- tactics version
theorem append_nil' (as : List α) : append as nil = as := by
  induction as with
    | nil =>
      rfl
    | cons a as' ih =>
      calc
        append (cons a as') nil = cons a (append as' nil) := by rfl
        _ = cons a as' := by rw [ih]

theorem append_assoc (as bs cs : List α)
  : append (append as bs) cs = append as (append bs cs) :=
  List.recOn
  (
    motive := fun as =>
      append (append as bs) cs  = append as (append bs cs)
  )
  as
  (
    show append (append nil bs) cs = append nil (append bs cs) from rfl
  )
  (
    fun (a : α) (as : List α)
      (ih : append (append as bs) cs = append as (append bs cs)) =>
      show append (append (cons a as) bs) cs = append (cons a as) (append bs cs) from
      calc
        append (append (cons a as) bs) cs
          = append (cons a (append as bs)) cs := by rw [cons_append]
        _ = cons a (append (append as bs) cs) := by rw [cons_append]
        _ = cons a (append as (append bs cs)) := by rw [ih]
        _ = append (cons a as) (append bs cs) := by rw [←cons_append]
  )

-- tactics version
theorem append_assoc' (as bs cs : List α)
  : append (append as bs) cs = append as (append bs cs) := by
  induction as with
  | nil =>
    calc
      append (append nil bs) cs = append nil (append bs cs) := by rfl
  | cons a as' ih =>
    -- show append (append (cons a as') bs) cs = append (cons a as') (append bs cs) from
    calc
      append (append (cons a as') bs) cs = append (cons a (append as' bs)) cs := by rfl
      _ = cons a (append (append as' bs) cs) := by rfl
      _ = cons a (append as' (append bs cs)) := by rw [ih]
      _ = append (cons a as') (append bs cs) := by rfl

end List
end Hidden
end Other_Recursive_Data_Types

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

section Inductive_Families
namespace Hidden'

inductive Eq {α : Sort u} (a : α) : α → Prop where
  | refl : Eq a a

theorem subst {α : Type u} {a b : α} {p : α → Prop} (h₁ : Eq a b) (h₂ : p a)
  : p b := by
  induction h₁ with
    | refl =>
      exact h₂

theorem symm {α : Type u} {a b : α} (h : Eq a b)
  : Eq b a := by
  induction h with
    | refl => exact Eq.refl

theorem trans {α : Type u} {a b c : α} (h₁ : Eq a b) (h₂ : Eq b c)
  : Eq a c := by
  induction h₁ with
    | refl => assumption

theorem congr {α β : Type u} {a b : α} (f : α → β) (h : Eq a b)
  : Eq (f a) (f b) := by
  induction h with
    | refl => exact Eq.refl

end Hidden'
end Inductive_Families

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

section ex1
namespace Hidden1
open Nat

def mul (m n : Nat) : Nat :=
  match n with
  | 0      => 0
  | succ n => m + mul m n

#eval mul 3 0
#eval mul 3 2

def pred (n : Nat) : Nat :=
  match n with
  | 0       => 0
  | succ n  => n

#eval pred 0
#eval pred 1
#eval pred 3

def trunc_sub (m n : Nat) : Nat :=
  match n with
  | 0       => m
  | succ n' => trunc_sub (pred m) n'

#eval trunc_sub 10 3
#eval trunc_sub 3 4
#eval trunc_sub 0 2

def exp (m n : Nat) : Nat :=
  match n with
  | 0       => 1
  | succ n' => mul m (exp m n')

#eval exp 2 0
#eval exp 2 3
#eval exp 3 2
#eval exp 0 0

end Hidden1
end ex1

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

section ex2
namespace Hidden2
open List

def length : List α → Nat
  | nil     => 0
  | _ :: as => 1 + length as

def reverse : List α → List α
  | nil     => nil
  | a :: as => reverse as ++ a :: nil

#eval length $ (nil : List Nat)
#eval length $ 1 :: 2 :: 3 :: nil

#eval reverse $ (nil : List Nat)
#eval reverse $ 3 :: 1 :: 4 :: 1 :: nil

theorem length_append (s t : List α)
  : length (s ++ t) = length s + length t :=
  List.recOn
  (
    motive := fun s =>
      length (s ++ t) = length s + length t
  )
  s
  (
    show length (nil ++ t) = length nil + length t from
    calc
      length (nil ++ t) = length t := by rw [List.nil_append]
      _ = 0 + length t := by rw [Nat.zero_add]
      _ = length nil + length t := by rfl
  )
  (
    fun (x : α) (s : List α)
      (ih : length (s ++ t) = length s + length t) =>
      show length (x :: s ++ t) = length (x :: s) + length t from
      calc
        length (x :: s ++ t) = length (x :: (s ++ t)) := by rw [cons_append]
        _ = 1 + length (s ++ t) := by rfl
        _ = 1 + (length s + length t) := by rw [ih]
        _ = (1 + length s) + length t := by rw [Nat.add_assoc]
        _ = length (x :: s) + length t := by rfl
  )

-- tactics version
theorem length_append' (s t : List α)
  : length (s ++ t) = length s + length t := by
  induction s with
  | nil =>
    calc
      length (nil ++ t) = length t := by rw [List.nil_append]
      _ = 0 + length t := by rw [Nat.zero_add]
      _ = length nil + length t := by rfl
  | cons head tail ih =>
    calc
      length (head :: tail ++ t) = length (head :: (tail ++ t)) := by rfl
      _ = 1 + length (tail ++ t) := by rfl
      _ = 1 + (length tail + length t) := by rw [ih]
      _ = 1 + length tail + length t := by simp [Nat.add_assoc]
      _ = length (head :: tail) + length t := by rfl

example (t : List α)
  : length (reverse t) = length t :=
  List.recOn
  (
    motive := fun t => length (reverse t) = length t
  )
  t
  (
    show length (reverse nil) = length nil from by rfl
  )
  (
    fun (x : α)(t : List α)
      (ih : length (reverse t) = length t) =>
      show length (reverse (x :: t)) = length (x :: t) from
      calc
        length (reverse (x :: t))
          = length (reverse t ++ x :: nil) := by rfl
        _ = length (reverse t) + length (x :: nil) := by rw [length_append]
        _ = length t + length (x :: nil) := by rw [ih]
        _ = length t + 1 := by rfl
        _ = 1 + length t := by rw [Nat.add_comm]
        _ = length (x :: t) := by rfl
  )

theorem reverse_nil : reverse (nil : List α) = nil := rfl

theorem reverse_append (s t : List α)
  : reverse (s ++ t) = reverse t ++ reverse s :=
  List.recOn
  (
    motive := fun s => reverse (s ++ t) = reverse t ++ reverse s
  )
  s
  (
    show reverse (nil ++ t) = reverse t ++ reverse nil from
    calc
      reverse (nil ++ t) = reverse t := by rw [nil_append]
      _ = reverse t ++ nil := by rw [append_nil]
      _ = reverse t ++ reverse nil := rfl
  )
  (
    fun (x : α) (s : List α)
      (ih : reverse (s ++ t) = reverse t ++ reverse s) =>
      show reverse (x :: s ++ t) = reverse t ++ reverse (x :: s) from
      calc
        reverse (x :: s ++ t)
          = reverse (x :: (s ++ t)) := by rw [cons_append]
        _ = reverse (s ++ t) ++ x :: nil := by rfl
        _ = reverse t ++ reverse s ++ x :: nil := by rw [ih]
        _ = reverse t ++ (reverse s ++ x :: nil) := by rw [append_assoc]
        _ = reverse t ++ (reverse (x :: s)) := by rfl
  )

example (t : List α)
  : reverse (reverse t) = t :=
  List.recOn
  (
    motive := fun t => reverse (reverse t) = t
  )
  t
  (
    show reverse (reverse nil) = nil from by rfl
  )
  (
    fun (x : α) (t : List α)
      (ih : reverse (reverse t) = t) =>
      show reverse (reverse (x :: t)) = x :: t from
      calc
        reverse (reverse (x :: t)) = reverse (reverse t ++ x :: nil) := by rfl
        _ = reverse (x :: nil) ++ reverse (reverse t) := by rw [reverse_append]
        _ = reverse (x :: nil) ++ t := by rw [ih]
        _ = reverse nil ++ x :: nil ++ t := by rfl
        _ = nil ++ x :: nil ++ t := by rfl
        _ = x :: nil ++ t := by rw [nil_append]
        _ = x :: (nil ++ t) := by rfl
        _ = x :: t := by rw [nil_append]
  )

end Hidden2
end ex2

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

section ex3
/-
Define an inductive data type
- const n,    a constant denoting the naturan number n
- var n,      a variable, numbered n
- plus s t,   the sum of s and t
- times s t,  the product of s and t
-/

inductive Term where
  | const : Nat → Term
  | var   : Nat → Term
  | plus  : Term → Term → Term
  | times : Term → Term → Term

open Term

def eval (v : Nat → Nat) : Term → Nat
  | const n   => n
  | var n     => v n
  | plus s t  => eval v s + eval v t
  | times s t => eval v s * eval v t

def const_3 : Term := const 3
def v1 : Term := var 1
def v2 : Term := var 2
def t1 : Term := plus (const 3) v1
def t2 : Term := times v2 t1
def vs : Nat → Nat
  | 1 => 4
  | 2 => 5
  | _ => 0
#eval eval vs const_3
#eval eval vs v1
#eval eval vs t1
#eval eval vs t2

end ex3


/-
Copyright (c) 2019 Zhouhang Zhou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Zhouhang Zhou, Yury Kudryashov, Heather Macbeth
-/
import measure_theory.integral.mean_inequalities
import topology.continuous_function.compact
import topology.metric_space.metrizable

/-!
# Density of simple functions

Show that each Borel measurable function can be approximated pointwise
by a sequence of simple functions.

## Main definitions

* `measure_theory.simple_func.nearest_pt (e : ℕ → α) (N : ℕ) : α →ₛ ℕ`: the `simple_func` sending
  each `x : α` to the point `e k` which is the nearest to `x` among `e 0`, ..., `e N`.
* `measure_theory.simple_func.approx_on (f : β → α) (hf : measurable f) (s : set α) (y₀ : α)
  (h₀ : y₀ ∈ s) [separable_space s] (n : ℕ) : β →ₛ α` : a simple function that takes values in `s`
  and approximates `f`.

## Main results

* `tendsto_approx_on` (pointwise convergence): If `f x ∈ s`, then the sequence of simple
  approximations `measure_theory.simple_func.approx_on f hf s y₀ h₀ n`, evaluated at `x`,
  tends to `f x` as `n` tends to `∞`.

## Notations

* `α →ₛ β` (local notation): the type of simple functions `α → β`.
-/

open set function filter topological_space ennreal emetric finset
open_locale classical topological_space ennreal measure_theory big_operators
variables {α β ι E F 𝕜 : Type*}

noncomputable theory

namespace measure_theory

local infixr ` →ₛ `:25 := simple_func

namespace simple_func

/-! ### Pointwise approximation by simple functions -/

variables [measurable_space α] [pseudo_emetric_space α] [opens_measurable_space α]

/-- `nearest_pt_ind e N x` is the index `k` such that `e k` is the nearest point to `x` among the
points `e 0`, ..., `e N`. If more than one point are at the same distance from `x`, then
`nearest_pt_ind e N x` returns the least of their indexes. -/
noncomputable def nearest_pt_ind (e : ℕ → α) : ℕ → α →ₛ ℕ
| 0 := const α 0
| (N + 1) := piecewise (⋂ k ≤ N, {x | edist (e (N + 1)) x < edist (e k) x})
    (measurable_set.Inter $ λ k, measurable_set.Inter_Prop $ λ hk,
      measurable_set_lt measurable_edist_right measurable_edist_right)
    (const α $ N + 1) (nearest_pt_ind N)

/-- `nearest_pt e N x` is the nearest point to `x` among the points `e 0`, ..., `e N`. If more than
one point are at the same distance from `x`, then `nearest_pt e N x` returns the point with the
least possible index. -/
noncomputable def nearest_pt (e : ℕ → α) (N : ℕ) : α →ₛ α :=
(nearest_pt_ind e N).map e

@[simp] lemma nearest_pt_ind_zero (e : ℕ → α) : nearest_pt_ind e 0 = const α 0 := rfl

@[simp] lemma nearest_pt_zero (e : ℕ → α) : nearest_pt e 0 = const α (e 0) := rfl

lemma nearest_pt_ind_succ (e : ℕ → α) (N : ℕ) (x : α) :
  nearest_pt_ind e (N + 1) x =
    if ∀ k ≤ N, edist (e (N + 1)) x < edist (e k) x
    then N + 1 else nearest_pt_ind e N x :=
by { simp only [nearest_pt_ind, coe_piecewise, set.piecewise], congr, simp }

lemma nearest_pt_ind_le (e : ℕ → α) (N : ℕ) (x : α) : nearest_pt_ind e N x ≤ N :=
begin
  induction N with N ihN, { simp },
  simp only [nearest_pt_ind_succ],
  split_ifs,
  exacts [le_rfl, ihN.trans N.le_succ]
end

lemma edist_nearest_pt_le (e : ℕ → α) (x : α) {k N : ℕ} (hk : k ≤ N) :
  edist (nearest_pt e N x) x ≤ edist (e k) x :=
begin
  induction N with N ihN generalizing k,
  { simp [nonpos_iff_eq_zero.1 hk, le_refl] },
  { simp only [nearest_pt, nearest_pt_ind_succ, map_apply],
    split_ifs,
    { rcases hk.eq_or_lt with rfl|hk,
      exacts [le_rfl, (h k (nat.lt_succ_iff.1 hk)).le] },
    { push_neg at h,
      rcases h with ⟨l, hlN, hxl⟩,
      rcases hk.eq_or_lt with rfl|hk,
      exacts [(ihN hlN).trans hxl, ihN (nat.lt_succ_iff.1 hk)] } }
end

lemma tendsto_nearest_pt {e : ℕ → α} {x : α} (hx : x ∈ closure (range e)) :
  tendsto (λ N, nearest_pt e N x) at_top (𝓝 x) :=
begin
  refine (at_top_basis.tendsto_iff nhds_basis_eball).2 (λ ε hε, _),
  rcases emetric.mem_closure_iff.1 hx ε hε with ⟨_, ⟨N, rfl⟩, hN⟩,
  rw [edist_comm] at hN,
  exact ⟨N, trivial, λ n hn, (edist_nearest_pt_le e x hn).trans_lt hN⟩
end

variables [measurable_space β] {f : β → α}

/-- Approximate a measurable function by a sequence of simple functions `F n` such that
`F n x ∈ s`. -/
noncomputable def approx_on (f : β → α) (hf : measurable f) (s : set α) (y₀ : α) (h₀ : y₀ ∈ s)
  [separable_space s] (n : ℕ) :
  β →ₛ α :=
by haveI : nonempty s := ⟨⟨y₀, h₀⟩⟩;
  exact comp (nearest_pt (λ k, nat.cases_on k y₀ (coe ∘ dense_seq s) : ℕ → α) n) f hf

@[simp] lemma approx_on_zero {f : β → α} (hf : measurable f) {s : set α} {y₀ : α} (h₀ : y₀ ∈ s)
  [separable_space s] (x : β) :
  approx_on f hf s y₀ h₀ 0 x = y₀ :=
rfl

lemma approx_on_mem {f : β → α} (hf : measurable f) {s : set α} {y₀ : α} (h₀ : y₀ ∈ s)
  [separable_space s] (n : ℕ) (x : β) :
  approx_on f hf s y₀ h₀ n x ∈ s :=
begin
  haveI : nonempty s := ⟨⟨y₀, h₀⟩⟩,
  suffices : ∀ n, (nat.cases_on n y₀ (coe ∘ dense_seq s) : α) ∈ s, { apply this },
  rintro (_|n),
  exacts [h₀, subtype.mem _]
end

@[simp] lemma approx_on_comp {γ : Type*} [measurable_space γ] {f : β → α} (hf : measurable f)
  {g : γ → β} (hg : measurable g) {s : set α} {y₀ : α} (h₀ : y₀ ∈ s) [separable_space s] (n : ℕ) :
  approx_on (f ∘ g) (hf.comp hg) s y₀ h₀ n = (approx_on f hf s y₀ h₀ n).comp g hg :=
rfl

lemma tendsto_approx_on {f : β → α} (hf : measurable f) {s : set α} {y₀ : α} (h₀ : y₀ ∈ s)
  [separable_space s] {x : β} (hx : f x ∈ closure s) :
  tendsto (λ n, approx_on f hf s y₀ h₀ n x) at_top (𝓝 $ f x) :=
begin
  haveI : nonempty s := ⟨⟨y₀, h₀⟩⟩,
  rw [← @subtype.range_coe _ s, ← image_univ, ← (dense_range_dense_seq s).closure_eq] at hx,
  simp only [approx_on, coe_comp],
  refine tendsto_nearest_pt (closure_minimal _ is_closed_closure hx),
  simp only [nat.range_cases_on, closure_union, range_comp coe],
  exact subset.trans (image_closure_subset_closure_image continuous_subtype_coe)
    (subset_union_right _ _)
end

lemma edist_approx_on_mono {f : β → α} (hf : measurable f) {s : set α} {y₀ : α} (h₀ : y₀ ∈ s)
  [separable_space s] (x : β) {m n : ℕ} (h : m ≤ n) :
  edist (approx_on f hf s y₀ h₀ n x) (f x) ≤ edist (approx_on f hf s y₀ h₀ m x) (f x) :=
begin
  dsimp only [approx_on, coe_comp, (∘)],
  exact edist_nearest_pt_le _ _ ((nearest_pt_ind_le _ _ _).trans h)
end

lemma edist_approx_on_le {f : β → α} (hf : measurable f) {s : set α} {y₀ : α} (h₀ : y₀ ∈ s)
  [separable_space s] (x : β) (n : ℕ) :
  edist (approx_on f hf s y₀ h₀ n x) (f x) ≤ edist y₀ (f x) :=
edist_approx_on_mono hf h₀ x (zero_le n)

lemma edist_approx_on_y0_le {f : β → α} (hf : measurable f) {s : set α} {y₀ : α} (h₀ : y₀ ∈ s)
  [separable_space s] (x : β) (n : ℕ) :
  edist y₀ (approx_on f hf s y₀ h₀ n x) ≤ edist y₀ (f x) + edist y₀ (f x) :=
calc edist y₀ (approx_on f hf s y₀ h₀ n x) ≤
  edist y₀ (f x) + edist (approx_on f hf s y₀ h₀ n x) (f x) : edist_triangle_right _ _ _
... ≤ edist y₀ (f x) + edist y₀ (f x) : add_le_add_left (edist_approx_on_le hf h₀ x n) _

end simple_func

end measure_theory


import .bool

namespace hidden

-- DATA TYPE

inductive bool_lang : Type
| TT : bool_lang
| FF : bool_lang
-- | var (v : bool_var) : bool_lang
| conj (e1 e2 : bool_lang) : bool_lang
| disj (e1 e2 : bool_lang) : bool_lang
| neg (e : bool_lang)

-- REFACTOR INTO TEST FILE
open bool_lang
def be1 := TT
def be2 := FF
def be3 := conj be1 be2
def be4 := neg be3

open boo

-- OPERATIONS
def eval : bool_lang → boo
| TT := tt
| FF := ff
-- | (var v) i := i v
| (conj e1 e2) := and (eval e1) (eval e2)
| (disj e1 e2) := or (eval e1) (eval e2)
| (neg e) := not (eval e)

-- NOTATIONS
notation b1 * b2 := conj b1 b2
notation b1 + b2 := disj b1 b2
prefix ! := neg

-- REFACTOR INTO TEST FILE
#reduce eval be4
#reduce eval (conj (disj be2 be4) be3)
#reduce eval ((be2 + be4) * be3)

/-
How do we know that eval terminates?
-/

/-
We must we insist that functions always
terminate. The following example shows if
we accept functions that infinitely loop,
then our logic is inconsistent insofar
as we can then have a proof of false. We
cannot have a proof of false in our logic,
so we cannot admit functions that are not
known the terminate.
-/

-- Non-terminating functions would be fatal
def bad : ℕ → false
| n := bad n    -- see this error message
#check (bad 3)  -- Yikes, a proof of false!

-- PROPERTIES?

/-
A property of an expression, e, is that e
evaluates to true. Let's call this property
evaluates_to_true.
-/
def evaluates_to_true : bool_lang → Prop
| e := eval e = boo.tt
/-
This definition broke when we added an interpretation,
i, as a second argument to eval.
-/

example : evaluates_to_true TT :=
begin
  unfold evaluates_to_true,
  trivial,
end
--ditto last comment

/-
Let's assert that TT*FF evaluates to
true. We won't be able to prove it.
-/
example : evaluates_to_true (TT * FF) :=
begin
  unfold evaluates_to_true,
  simp [eval],
  apply rfl,  -- it's just not true!
end
--ditto last comment

-- This time our proof will go through
example : evaluates_to_true (TT * TT) :=
begin
  unfold evaluates_to_true,
  simp [eval],
  apply rfl,  -- false
end
--ditto last comment

/-
The take-away concept from the preceding
examples is that we can formalize the
a *property* of an expression in a given
language (here our bool_lang language).
-/

/-
We can also formalize properties of a
language as a whole. The example we'll
use here is that we can state and prove
the proposition that semantic evaluation
in bool_lang is deterministic. If we
evaluate the meaning of an expression
twice, we should always get the same
answer.
-/
-- Property of the language as a whole:
-- evaluation is deterministic.

-- HOMEWORK

/-
What deterministic means is that if you evaluate the same
expression twice (in the same environment) that you'll get
the same answer. A language with a statement that returns
a truly random number wouldn't have this property. Show that
evaluation is deterministic for our "Bool" language.
-/

def eval_is_deterministic :
  ∀ (e : bool_lang) (b1 b2 : boo),
  b1 = (eval e) →
  b2 = (eval e) →
  b1 = b2 :=
begin
  intros e b1 b2 h1 h2,
  rw h1,
  rw h2,
end

-- What is the type of eval_is_deterministic
#check @eval_is_deterministic

end hidden


/-
Copyright (c) 2014 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro

! This file was ported from Lean 3 source module data.num.bitwise
! leanprover-community/mathlib commit f7fc89d5d5ff1db2d1242c7bb0e9062ce47ef47c
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Num.Basic
import Mathlib.Data.Bitvec.Core

/-!
# Bitwise operations using binary representation of integers

## Definitions

* bitwise operations for `PosNum` and `Num`,
* `SNum`, a type that represents integers as a bit string with a sign bit at the end,
* arithmetic operations for `SNum`.
-/

namespace PosNum

/-- Bitwise "or" for `PosNum`. -/
def lor : PosNum → PosNum → PosNum
  | 1, bit0 q => bit1 q
  | 1, q => q
  | bit0 p, 1 => bit1 p
  | p, 1 => p
  | bit0 p, bit0 q => bit0 (lor p q)
  | bit0 p, bit1 q => bit1 (lor p q)
  | bit1 p, bit0 q => bit1 (lor p q)
  | bit1 p, bit1 q => bit1 (lor p q)
#align pos_num.lor PosNum.lor

/-- Bitwise "and" for `PosNum`. -/
def land : PosNum → PosNum → Num
  | 1, bit0 _ => 0
  | 1, _ => 1
  | bit0 _, 1 => 0
  | _, 1 => 1
  | bit0 p, bit0 q => Num.bit0 (land p q)
  | bit0 p, bit1 q => Num.bit0 (land p q)
  | bit1 p, bit0 q => Num.bit0 (land p q)
  | bit1 p, bit1 q => Num.bit1 (land p q)
#align pos_num.land PosNum.land

/-- Bitwise `fun a b ↦ a && !b` for `PosNum`. For example, `ldiff 5 9 = 4`:
```
 101
1001
----
 100
```
-/
def ldiff : PosNum → PosNum → Num
  | 1, bit0 _ => 1
  | 1, _ => 0
  | bit0 p, 1 => Num.pos (bit0 p)
  | bit1 p, 1 => Num.pos (bit0 p)
  | bit0 p, bit0 q => Num.bit0 (ldiff p q)
  | bit0 p, bit1 q => Num.bit0 (ldiff p q)
  | bit1 p, bit0 q => Num.bit1 (ldiff p q)
  | bit1 p, bit1 q => Num.bit0 (ldiff p q)
#align pos_num.ldiff PosNum.ldiff

/-- Bitwise "xor" for `PosNum`. -/
def lxor : PosNum → PosNum → Num
  | 1, 1 => 0
  | 1, bit0 q => Num.pos (bit1 q)
  | 1, bit1 q => Num.pos (bit0 q)
  | bit0 p, 1 => Num.pos (bit1 p)
  | bit1 p, 1 => Num.pos (bit0 p)
  | bit0 p, bit0 q => Num.bit0 (lxor p q)
  | bit0 p, bit1 q => Num.bit1 (lxor p q)
  | bit1 p, bit0 q => Num.bit1 (lxor p q)
  | bit1 p, bit1 q => Num.bit0 (lxor p q)
#align pos_num.lxor PosNum.lxor

/-- `a.testBit n` is `true` iff the `n`-th bit (starting from the LSB) in the binary representation
      of `a` is active. If the size of `a` is less than `n`, this evaluates to `false`. -/
def testBit : PosNum → Nat → Bool
  | 1, 0 => true
  | 1, _ => false
  | bit0 _, 0 => false
  | bit0 p, n + 1 => testBit p n
  | bit1 _, 0 => true
  | bit1 p, n + 1 => testBit p n
#align pos_num.test_bit PosNum.testBit

/-- `n.oneBits 0` is the list of indices of active bits in the binary representation of `n`. -/
def oneBits : PosNum → Nat → List Nat
  | 1, d => [d]
  | bit0 p, d => oneBits p (d + 1)
  | bit1 p, d => d :: oneBits p (d + 1)
#align pos_num.one_bits PosNum.oneBits

/-- Left-shift the binary representation of a `PosNum`. -/
def shiftl : PosNum → Nat → PosNum
  | p, 0 => p
  | p, n + 1 => shiftl p.bit0 n
#align pos_num.shiftl PosNum.shiftl

-- Porting note: `PosNum.shiftl` is defined as tail-recursive in Lean4.
--               This theorem ensures the definition is same to one in Lean3.
theorem shiftl_succ_eq_bit0_shiftl : ∀ (p : PosNum) (n : Nat), shiftl p n.succ = bit0 (shiftl p n)
  | _, 0       => rfl
  | p, .succ n => shiftl_succ_eq_bit0_shiftl p.bit0 n

/-- Right-shift the binary representation of a `PosNum`. -/
def shiftr : PosNum → Nat → Num
  | p, 0 => Num.pos p
  | 1, _ => 0
  | bit0 p, n + 1 => shiftr p n
  | bit1 p, n + 1 => shiftr p n
#align pos_num.shiftr PosNum.shiftr

end PosNum

namespace Num

/-- Bitwise "or" for `Num`. -/
def lor : Num → Num → Num
  | 0, q => q
  | p, 0 => p
  | pos p, pos q => pos (p.lor q)
#align num.lor Num.lor

/-- Bitwise "and" for `Num`. -/
def land : Num → Num → Num
  | 0, _ => 0
  | _, 0 => 0
  | pos p, pos q => p.land q
#align num.land Num.land

/-- Bitwise `fun a b ↦ a && !b` for `Num`. For example, `ldiff 5 9 = 4`:
```
 101
1001
----
 100
```
-/
def ldiff : Num → Num → Num
  | 0, _ => 0
  | p, 0 => p
  | pos p, pos q => p.ldiff q
#align num.ldiff Num.ldiff

/-- Bitwise "xor" for `Num`. -/
def lxor : Num → Num → Num
  | 0, q => q
  | p, 0 => p
  | pos p, pos q => p.lxor q
#align num.lxor Num.lxor

/-- Left-shift the binary representation of a `Num`. -/
def shiftl : Num → Nat → Num
  | 0, _ => 0
  | pos p, n => pos (p.shiftl n)
#align num.shiftl Num.shiftl

/-- Right-shift the binary representation of a `Num`. -/
def shiftr : Num → Nat → Num
  | 0, _ => 0
  | pos p, n => p.shiftr n
#align num.shiftr Num.shiftr

/-- `a.testBit n` is `true` iff the `n`-th bit (starting from the LSB) in the binary representation
      of `a` is active. If the size of `a` is less than `n`, this evaluates to `false`. -/
def testBit : Num → Nat → Bool
  | 0, _ => false
  | pos p, n => p.testBit n
#align num.test_bit Num.testBit

/-- `n.oneBits` is the list of indices of active bits in the binary representation of `n`. -/
def oneBits : Num → List Nat
  | 0 => []
  | pos p => p.oneBits 0
#align num.one_bits Num.oneBits

end Num

/-- This is a nonzero (and "non minus one") version of `SNum`.
    See the documentation of `SNum` for more details. -/
inductive NzsNum : Type
  | msb : Bool → NzsNum
  /-- Add a bit at the end of a `NzsNum`. -/
  | bit : Bool → NzsNum → NzsNum
  deriving DecidableEq  -- Porting note: Removed `deriving has_reflect`.
#align nzsnum NzsNum

/-- Alternative representation of integers using a sign bit at the end.
  The convention on sign here is to have the argument to `msb` denote
  the sign of the MSB itself, with all higher bits set to the negation
  of this sign. The result is interpreted in two's complement.

     13  = ..0001101(base 2) = nz (bit1 (bit0 (bit1 (msb true))))
     -13 = ..1110011(base 2) = nz (bit1 (bit1 (bit0 (msb false))))

  As with `Num`, a special case must be added for zero, which has no msb,
  but by two's complement symmetry there is a second special case for -1.
  Here the `Bool` field indicates the sign of the number.

     0  = ..0000000(base 2) = zero false
     -1 = ..1111111(base 2) = zero true -/
inductive SNum : Type
  | zero : Bool → SNum
  | nz : NzsNum → SNum
  deriving DecidableEq  -- Porting note: Removed `deriving has_reflect`.
#align snum SNum

instance : Coe NzsNum SNum :=
  ⟨SNum.nz⟩

instance : Zero SNum :=
  ⟨SNum.zero false⟩

instance : One NzsNum :=
  ⟨NzsNum.msb true⟩

instance : One SNum :=
  ⟨SNum.nz 1⟩

instance : Inhabited NzsNum :=
  ⟨1⟩

instance : Inhabited SNum :=
  ⟨0⟩

/-!
The `SNum` representation uses a bit string, essentially a list of 0 (`false`) and 1 (`true`) bits,
and the negation of the MSB is sign-extended to all higher bits.
-/

namespace NzsNum

-- mathport name: nznum.bit
@[inherit_doc]
scoped notation a "::" b => bit a b

/-- Sign of a `NzsNum`. -/
def sign : NzsNum → Bool
  | msb b => not b
  | _ :: p => sign p
#align nzsnum.sign NzsNum.sign

/-- Bitwise `not` for `NzsNum`. -/
@[match_pattern]
def not : NzsNum → NzsNum
  | msb b => msb (Not b)
  | b :: p => Not b :: not p
#align nzsnum.not NzsNum.not

-- mathport name: «expr~ »
@[inherit_doc]
scoped prefix:100 "~" => not

/-- Add an inactive bit at the end of a `NzsNum`. This mimics `PosNum.bit0`. -/
def bit0 : NzsNum → NzsNum :=
  bit false
#align nzsnum.bit0 NzsNum.bit0

/-- Add an active bit at the end of a `NzsNum`. This mimics `PosNum.bit1`. -/
def bit1 : NzsNum → NzsNum :=
  bit true
#align nzsnum.bit1 NzsNum.bit1

/-- The `head` of a `NzsNum` is the boolean value of its LSB. -/
def head : NzsNum → Bool
  | msb b => b
  | b :: _ => b
#align nzsnum.head NzsNum.head

/-- The `tail` of a `NzsNum` is the `SNum` obtained by removing the LSB.
      Edge cases: `tail 1 = 0` and `tail (-2) = -1`. -/
def tail : NzsNum → SNum
  | msb b => SNum.zero (Not b)
  | _ :: p => p
#align nzsnum.tail NzsNum.tail

end NzsNum

namespace SNum

open NzsNum

/-- Sign of a `SNum`. -/
def sign : SNum → Bool
  | zero z => z
  | nz p => p.sign
#align snum.sign SNum.sign

/-- Bitwise `not` for `SNum`. -/
@[match_pattern]
def not : SNum → SNum
  | zero z => zero (Not z)
  | nz p => ~p
#align snum.not SNum.not

-- Porting note: Defined `priority` so that `~1 : SNum` is unambiguous.
-- mathport name: snum.not
@[inherit_doc]
scoped prefix:100 (priority := default + 1) "~" => not

/-- Add a bit at the end of a `SNum`. This mimics `NzsNum.bit`. -/
@[match_pattern]
def bit : Bool → SNum → SNum
  | b, zero z => if b = z then zero b else msb b
  | b, nz p => p.bit b
#align snum.bit SNum.bit

-- mathport name: snum.bit
@[inherit_doc]
scoped notation a "::" b => bit a b

/-- Add an inactive bit at the end of a `SNum`. This mimics `ZNum.bit0`. -/
def bit0 : SNum → SNum :=
  bit false
#align snum.bit0 SNum.bit0

/-- Add an active bit at the end of a `SNum`. This mimics `ZNum.bit1`. -/
def bit1 : SNum → SNum :=
  bit true
#align snum.bit1 SNum.bit1

theorem bit_zero (b : Bool) : (b :: zero b) = zero b := by cases b <;> rfl
#align snum.bit_zero SNum.bit_zero

theorem bit_one (b : Bool) : (b :: zero (Not b)) = msb b := by cases b <;> rfl
#align snum.bit_one SNum.bit_one

end SNum

namespace NzsNum

open SNum

/-- A dependent induction principle for `NzsNum`, with base cases
      `0 : SNum` and `(-1) : SNum`. -/
def drec' {C : SNum → Sort _} (z : ∀ b, C (SNum.zero b)) (s : ∀ b p, C p → C (b :: p)) :
    ∀ p : NzsNum, C p
  | msb b => by rw [← bit_one] ; exact s b (SNum.zero (Not b)) (z (Not b))
  | bit b p => s b p (drec' z s p)
#align nzsnum.drec' NzsNum.drec'

end NzsNum

namespace SNum

open NzsNum

/-- The `head` of a `SNum` is the boolean value of its LSB. -/
def head : SNum → Bool
  | zero z => z
  | nz p => p.head
#align snum.head SNum.head

/-- The `tail` of a `SNum` is obtained by removing the LSB.
      Edge cases: `tail 1 = 0`, `tail (-2) = -1`, `tail 0 = 0` and `tail (-1) = -1`. -/
def tail : SNum → SNum
  | zero z => zero z
  | nz p => p.tail
#align snum.tail SNum.tail

/-- A dependent induction principle for `SNum` which avoids relying on `NzsNum`. -/
def drec' {C : SNum → Sort _} (z : ∀ b, C (SNum.zero b)) (s : ∀ b p, C p → C (b :: p)) : ∀ p, C p
  | zero b => z b
  | nz p => p.drec' z s
#align snum.drec' SNum.drec'

/-- An induction principle for `SNum` which avoids relying on `NzsNum`. -/
def rec' {α} (z : Bool → α) (s : Bool → SNum → α → α) : SNum → α :=
  drec' z s
#align snum.rec' SNum.rec'

/-- `SNum.testBit n a` is `true` iff the `n`-th bit (starting from the LSB) of `a` is active.
      If the size of `a` is less than `n`, this evaluates to `false`. -/
def testBit : Nat → SNum → Bool
  | 0, p => head p
  | n + 1, p => testBit n (tail p)
#align snum.test_bit SNum.testBit

/-- The successor of a `SNum` (i.e. the operation adding one). -/
def succ : SNum → SNum :=
  rec' (fun b ↦ cond b 0 1) fun b p succp ↦ cond b (false :: succp) (true :: p)
#align snum.succ SNum.succ

/-- The predecessor of a `SNum` (i.e. the operation of removing one). -/
def pred : SNum → SNum :=
  rec' (fun b ↦ cond b (~1) (~0)) fun b p predp ↦ cond b (false :: p) (true :: predp)
#align snum.pred SNum.pred

/-- The opposite of a `SNum`. -/
protected def neg (n : SNum) : SNum :=
  succ (~n)
#align snum.neg SNum.neg

instance : Neg SNum :=
  ⟨SNum.neg⟩

/-- `SNum.czAdd a b n` is `n + a - b` (where `a` and `b` should be read as either 0 or 1).
      This is useful to implement the carry system in `cAdd`. -/
def czAdd : Bool → Bool → SNum → SNum
  | false, false, p => p
  | false, true, p => pred p
  | true, false, p => succ p
  | true, true, p => p
#align snum.czadd SNum.czAdd

end SNum

namespace SNum

/-- `a.bits n` is the vector of the `n` first bits of `a` (starting from the LSB). -/
def bits : SNum → ∀ n, Vector Bool n
  | _, 0 => Vector.nil
  | p, n + 1 => head p ::ᵥ bits (tail p) n
#align snum.bits SNum.bits

/-- `SNum.cAdd n m a` is `n + m + a` (where `a` should be read as either 0 or 1).
      `a` represents a carry bit. -/
def cAdd : SNum → SNum → Bool → SNum :=
  rec' (fun a p c ↦ czAdd c a p) fun a p IH ↦
    rec' (fun b c ↦ czAdd c b (a :: p)) fun b q _ c ↦ Bitvec.xor3 a b c :: IH q (Bitvec.carry a b c)
#align snum.cadd SNum.cAdd

/-- Add two `SNum`s. -/
protected def add (a b : SNum) : SNum :=
  cAdd a b false
#align snum.add SNum.add

instance : Add SNum :=
  ⟨SNum.add⟩

/-- Subtract two `SNum`s. -/
protected def sub (a b : SNum) : SNum :=
  a + -b
#align snum.sub SNum.sub

instance : Sub SNum :=
  ⟨SNum.sub⟩

/-- Multiply two `SNum`s. -/
protected def mul (a : SNum) : SNum → SNum :=
  rec' (fun b ↦ cond b (-a) 0) fun b _ IH ↦ cond b (bit0 IH + a) (bit0 IH)
#align snum.mul SNum.mul

instance : Mul SNum :=
  ⟨SNum.mul⟩

end SNum


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

/-!
# Integration of specific interval integrals

This file contains proofs of the integrals of various specific functions. This includes:
* Integrals of simple functions, such as `id`, `pow`, `exp`, `inv`
* Integrals of some trigonometric functions, such as `sin`, `cos`, `1 / (1 + x^2)`
* The integral of `cos x ^ 2 - sin x ^ 2`
* Reduction formulae for the integrals of `sin x ^ n` and `cos x ^ n` for `n ≥ 2`
* The computation of `∫ x in 0..π, sin x ^ n` as a product for even and odd `n` (used in proving the
  Wallis product for pi)

With these lemmas, many simple integrals can be computed by `simp` or `norm_num`.
See `test/integration.lean` for specific examples.

This file also contains some facts about the interval integrability of specific functions.

This file is still being developed.

## Tags

integrate, integration, integrable, integrability
-/

open real nat set finset
open_locale real big_operators
variables {a b : ℝ} (n : ℕ)

namespace interval_integral
open measure_theory
variables {f : ℝ → ℝ} {μ ν : measure ℝ} [locally_finite_measure μ] (c d : ℝ)

/-! ### Interval integrability -/

@[simp]
lemma interval_integrable_pow : interval_integrable (λ x, x^n) μ a b :=
(continuous_pow n).interval_integrable a b

@[simp]
lemma interval_integrable_id : interval_integrable (λ x, x) μ a b :=
continuous_id.interval_integrable a b

@[simp]
lemma interval_integrable_const : interval_integrable (λ x, c) μ a b :=
continuous_const.interval_integrable a b

@[simp]
lemma interval_integrable.const_mul (h : interval_integrable f ν a b) :
  interval_integrable (λ x, c * f x) ν a b :=
by convert h.smul c

@[simp]
lemma interval_integrable.mul_const (h : interval_integrable f ν a b) :
  interval_integrable (λ x, f x * c) ν a b :=
by simp only [mul_comm, interval_integrable.const_mul c h]

@[simp]
lemma interval_integrable.div (h : interval_integrable f ν a b) :
  interval_integrable (λ x, f x / c) ν a b :=
interval_integrable.mul_const c⁻¹ h

lemma interval_integrable_one_div (h : ∀ x : ℝ, x ∈ interval a b → f x ≠ 0)
  (hf : continuous_on f (interval a b)) :
  interval_integrable (λ x, 1 / f x) μ a b :=
(continuous_on_const.div hf h).interval_integrable

@[simp]
lemma interval_integrable_inv (h : ∀ x : ℝ, x ∈ interval a b → f x ≠ 0)
  (hf : continuous_on f (interval a b)) :
  interval_integrable (λ x, (f x)⁻¹) μ a b :=
by simpa only [one_div] using interval_integrable_one_div h hf

@[simp]
lemma interval_integrable_exp : interval_integrable exp μ a b :=
continuous_exp.interval_integrable a b

@[simp]
lemma interval_integrable_sin : interval_integrable sin μ a b :=
continuous_sin.interval_integrable a b

@[simp]
lemma interval_integrable_cos : interval_integrable cos μ a b :=
continuous_cos.interval_integrable a b

lemma interval_integrable_one_div_one_add_sq : interval_integrable (λ x : ℝ, 1 / (1 + x^2)) μ a b :=
begin
  refine (continuous_const.div _ (λ x, _)).interval_integrable a b,
  { continuity },
  { nlinarith },
end

@[simp]
lemma interval_integrable_inv_one_add_sq : interval_integrable (λ x : ℝ, (1 + x^2)⁻¹) μ a b :=
by simpa only [one_div] using interval_integrable_one_div_one_add_sq

/-! ### Integral of a function scaled by a constant -/

@[simp]
lemma integral_const_mul : ∫ x in a..b, c * f x = c * ∫ x in a..b, f x :=
integral_smul c

@[simp]
lemma integral_mul_const : ∫ x in a..b, f x * c = (∫ x in a..b, f x) * c :=
by simp only [mul_comm, integral_const_mul]

@[simp]
lemma integral_div : ∫ x in a..b, f x / c = (∫ x in a..b, f x) / c :=
integral_mul_const c⁻¹

/-! ### Integrals of the form `c * ∫ x in a..b, f (c * x + d)` -/

@[simp]
lemma mul_integral_comp_mul_right : c * ∫ x in a..b, f (x * c) = ∫ x in a*c..b*c, f x :=
smul_integral_comp_mul_right f c

@[simp]
lemma mul_integral_comp_mul_left : c * ∫ x in a..b, f (c * x) = ∫ x in c*a..c*b, f x :=
smul_integral_comp_mul_left f c

@[simp]
lemma inv_mul_integral_comp_div : c⁻¹ * ∫ x in a..b, f (x / c) = ∫ x in a/c..b/c, f x :=
inv_smul_integral_comp_div f c

@[simp]
lemma mul_integral_comp_mul_add : c * ∫ x in a..b, f (c * x + d) = ∫ x in c*a+d..c*b+d, f x :=
smul_integral_comp_mul_add f c d

@[simp]
lemma mul_integral_comp_add_mul : c * ∫ x in a..b, f (d + c * x) = ∫ x in d+c*a..d+c*b, f x :=
smul_integral_comp_add_mul f c d

@[simp]
lemma inv_mul_integral_comp_div_add : c⁻¹ * ∫ x in a..b, f (x / c + d) = ∫ x in a/c+d..b/c+d, f x :=
inv_smul_integral_comp_div_add f c d

@[simp]
lemma inv_mul_integral_comp_add_div : c⁻¹ * ∫ x in a..b, f (d + x / c) = ∫ x in d+a/c..d+b/c, f x :=
inv_smul_integral_comp_add_div f c d

@[simp]
lemma mul_integral_comp_mul_sub : c * ∫ x in a..b, f (c * x - d) = ∫ x in c*a-d..c*b-d, f x :=
smul_integral_comp_mul_sub f c d

@[simp]
lemma mul_integral_comp_sub_mul : c * ∫ x in a..b, f (d - c * x) = ∫ x in d-c*b..d-c*a, f x :=
smul_integral_comp_sub_mul f c d

@[simp]
lemma inv_mul_integral_comp_div_sub : c⁻¹ * ∫ x in a..b, f (x / c - d) = ∫ x in a/c-d..b/c-d, f x :=
inv_smul_integral_comp_div_sub f c d

@[simp]
lemma inv_mul_integral_comp_sub_div : c⁻¹ * ∫ x in a..b, f (d - x / c) = ∫ x in d-b/c..d-a/c, f x :=
inv_smul_integral_comp_sub_div f c d

end interval_integral

open interval_integral

/-! ### Integrals of simple functions -/

@[simp]
lemma integral_pow : ∫ x in a..b, x ^ n = (b ^ (n + 1) - a ^ (n + 1)) / (n + 1) :=
begin
  have hderiv : deriv (λ x : ℝ, x ^ (n + 1) / (n + 1)) = λ x, x ^ n,
  { ext,
    have hne : (n + 1 : ℝ) ≠ 0 := by exact_mod_cast succ_ne_zero n,
    simp [mul_div_assoc, mul_div_cancel' _ hne] },
  rw integral_deriv_eq_sub' _ hderiv;
  norm_num [div_sub_div_same, continuous_on_pow],
end

@[simp]
lemma integral_id : ∫ x in a..b, x = (b ^ 2 - a ^ 2) / 2 :=
by simpa using integral_pow 1

@[simp]
lemma integral_one : ∫ x in a..b, (1 : ℝ) = b - a :=
by simp only [mul_one, smul_eq_mul, integral_const]

@[simp]
lemma integral_exp : ∫ x in a..b, exp x = exp b - exp a :=
by rw integral_deriv_eq_sub'; norm_num [continuous_on_exp]

@[simp]
lemma integral_inv (h : (0:ℝ) ∉ interval a b) : ∫ x in a..b, x⁻¹ = log (b / a) :=
begin
  have h' := λ x hx, ne_of_mem_of_not_mem hx h,
  rw [integral_deriv_eq_sub' _ deriv_log' (λ x hx, differentiable_at_log (h' x hx))
        (continuous_on_inv'.mono $ subset_compl_singleton_iff.mpr h),
      log_div (h' b right_mem_interval) (h' a left_mem_interval)],
end

@[simp]
lemma integral_inv_of_pos (ha : 0 < a) (hb : 0 < b) : ∫ x in a..b, x⁻¹ = log (b / a) :=
integral_inv $ not_mem_interval_of_lt ha hb

@[simp]
lemma integral_inv_of_neg (ha : a < 0) (hb : b < 0) : ∫ x in a..b, x⁻¹ = log (b / a) :=
integral_inv $ not_mem_interval_of_gt ha hb

lemma integral_one_div (h : (0:ℝ) ∉ interval a b) : ∫ x : ℝ in a..b, 1/x = log (b / a) :=
by simp only [one_div, integral_inv h]

lemma integral_one_div_of_pos (ha : 0 < a) (hb : 0 < b) : ∫ x : ℝ in a..b, 1/x = log (b / a) :=
by simp only [one_div, integral_inv_of_pos ha hb]

lemma integral_one_div_of_neg (ha : a < 0) (hb : b < 0) : ∫ x : ℝ in a..b, 1/x = log (b / a) :=
by simp only [one_div, integral_inv_of_neg ha hb]

@[simp]
lemma integral_sin : ∫ x in a..b, sin x = cos a - cos b :=
by rw integral_deriv_eq_sub' (λ x, -cos x); norm_num [continuous_on_sin]

@[simp]
lemma integral_cos : ∫ x in a..b, cos x = sin b - sin a :=
by rw integral_deriv_eq_sub'; norm_num [continuous_on_cos]

lemma integral_cos_sq_sub_sin_sq :
  ∫ x in a..b, cos x ^ 2 - sin x ^ 2 = sin b * cos b - sin a * cos a :=
by simpa only [sq, sub_eq_add_neg, neg_mul_eq_mul_neg] using integral_deriv_mul_eq_sub
  (λ x hx, has_deriv_at_sin x) (λ x hx, has_deriv_at_cos x) continuous_on_cos continuous_on_sin.neg

@[simp]
lemma integral_inv_one_add_sq : ∫ x : ℝ in a..b, (1 + x^2)⁻¹ = arctan b - arctan a :=
begin
  simp only [← one_div],
  refine integral_deriv_eq_sub' _ _ _ (continuous_const.div _ (λ x, _)).continuous_on,
  { norm_num },
  { norm_num },
  { continuity },
  { nlinarith },
end

lemma integral_one_div_one_add_sq : ∫ x : ℝ in a..b, 1 / (1 + x^2) = arctan b - arctan a :=
by simp only [one_div, integral_inv_one_add_sq]

/-! ### Integral of `sin x ^ n` -/

lemma integral_sin_pow_aux :
  ∫ x in a..b, sin x ^ (n + 2) = sin a ^ (n + 1) * cos a - sin b ^ (n + 1) * cos b
    + (n + 1) * (∫ x in a..b, sin x ^ n) - (n + 1) * ∫ x in a..b, sin x ^ (n + 2) :=
begin
  let C := sin a ^ (n + 1) * cos a - sin b ^ (n + 1) * cos b,
  have h : ∀ α β γ : ℝ, α * (β * α * γ) = β * (α * α * γ) := λ α β γ, by ring,
  have hu : ∀ x ∈ _, has_deriv_at (λ y, sin y ^ (n + 1)) ((n + 1) * cos x * sin x ^ n) x :=
    λ x hx, by simpa [mul_right_comm] using (has_deriv_at_sin x).pow,
  have hv : ∀ x ∈ interval a b, has_deriv_at (-cos) (sin x) x :=
    λ x hx, by simpa only [neg_neg] using (has_deriv_at_cos x).neg,
  have H := integral_mul_deriv_eq_deriv_mul hu hv _ _,
  calc  ∫ x in a..b, sin x ^ (n + 2)
      = ∫ x in a..b, sin x ^ (n + 1) * sin x : by simp only [pow_succ']
  ... = C + (n + 1) * ∫ x in a..b, cos x ^ 2 * sin x ^ n : by simp [H, h, sq]
  ... = C + (n + 1) * ∫ x in a..b, sin x ^ n - sin x ^ (n + 2) : by simp [cos_sq', sub_mul,
                                                                          ← pow_add, add_comm]
  ... = C + (n + 1) * (∫ x in a..b, sin x ^ n) - (n + 1) * ∫ x in a..b, sin x ^ (n + 2) :
    by rw [integral_sub, mul_sub, add_sub_assoc]; apply continuous.interval_integrable; continuity,
  all_goals { apply continuous.continuous_on, continuity },
end

/-- The reduction formula for the integral of `sin x ^ n` for any natural `n ≥ 2`. -/
lemma integral_sin_pow :
  ∫ x in a..b, sin x ^ (n + 2) = (sin a ^ (n + 1) * cos a - sin b ^ (n + 1) * cos b) / (n + 2)
    + (n + 1) / (n + 2) * ∫ x in a..b, sin x ^ n :=
begin
  have : (n : ℝ) + 2 ≠ 0 := by exact_mod_cast succ_ne_zero n.succ,
  field_simp,
  convert eq_sub_iff_add_eq.mp (integral_sin_pow_aux n),
  ring,
end

@[simp]
lemma integral_sin_sq : ∫ x in a..b, sin x ^ 2 = (sin a * cos a - sin b * cos b + b - a) / 2 :=
by field_simp [integral_sin_pow, add_sub_assoc]

theorem integral_sin_pow_odd :
  ∫ x in 0..π, sin x ^ (2 * n + 1) = 2 * ∏ i in range n, (2 * i + 2) / (2 * i + 3) :=
begin
  induction n with k ih, { norm_num },
  rw [prod_range_succ_comm, mul_left_comm, ← ih, mul_succ, integral_sin_pow],
  norm_cast,
  simp [-cast_add] with field_simps,
end

theorem integral_sin_pow_even :
  ∫ x in 0..π, sin x ^ (2 * n) = π * ∏ i in range n, (2 * i + 1) / (2 * i + 2) :=
begin
  induction n with k ih, { simp },
  rw [prod_range_succ_comm, mul_left_comm, ← ih, mul_succ, integral_sin_pow],
  norm_cast,
  simp [-cast_add] with field_simps,
end

lemma integral_sin_pow_pos : 0 < ∫ x in 0..π, sin x ^ n :=
begin
  rcases even_or_odd' n with ⟨k, (rfl | rfl)⟩;
  simp only [integral_sin_pow_even, integral_sin_pow_odd];
  refine mul_pos (by norm_num [pi_pos]) (prod_pos (λ n hn, div_pos _ _));
  norm_cast;
  linarith,
end

lemma integral_sin_pow_antimono :
  ∫ x in 0..π, sin x ^ (n + 1) ≤ ∫ x in 0..π, sin x ^ n :=
begin
  refine integral_mono_on _ _ pi_pos.le (λ x hx, _),
  { exact ((continuous_pow (n + 1)).comp continuous_sin).interval_integrable 0 π },
  { exact ((continuous_pow n).comp continuous_sin).interval_integrable 0 π },
  { refine pow_le_pow_of_le_one (sin_nonneg_of_mem_Icc _) (sin_le_one x) (nat.le_add_right n 1),
    rwa interval_of_le pi_pos.le at hx },
end

/-! ### Integral of `cos x ^ n` -/

lemma integral_cos_pow_aux :
  ∫ x in a..b, cos x ^ (n + 2) = cos b ^ (n + 1) * sin b - cos a ^ (n + 1) * sin a
    + (n + 1) * (∫ x in a..b, cos x ^ n) - (n + 1) * ∫ x in a..b, cos x ^ (n + 2) :=
begin
  let C := cos b ^ (n + 1) * sin b - cos a ^ (n + 1) * sin a,
  have h : ∀ α β γ : ℝ, α * (β * α * γ) = β * (α * α * γ) := λ α β γ, by ring,
  have hu : ∀ x ∈ _, has_deriv_at (λ y, cos y ^ (n + 1)) (-(n + 1) * sin x * cos x ^ n) x :=
    λ x hx, by simpa [mul_right_comm, -neg_add_rev] using (has_deriv_at_cos x).pow,
  have hv : ∀ x ∈ interval a b, has_deriv_at sin (cos x) x := λ x hx, has_deriv_at_sin x,
  have H := integral_mul_deriv_eq_deriv_mul hu hv _ _,
  calc  ∫ x in a..b, cos x ^ (n + 2)
      = ∫ x in a..b, cos x ^ (n + 1) * cos x : by simp only [pow_succ']
  ... = C + (n + 1) * ∫ x in a..b, sin x ^ 2 * cos x ^ n : by simp [H, h, sq, -neg_add_rev]
  ... = C + (n + 1) * ∫ x in a..b, cos x ^ n - cos x ^ (n + 2) : by simp [sin_sq, sub_mul,
                                                                          ← pow_add, add_comm]
  ... = C + (n + 1) * (∫ x in a..b, cos x ^ n) - (n + 1) * ∫ x in a..b, cos x ^ (n + 2) :
    by rw [integral_sub, mul_sub, add_sub_assoc]; apply continuous.interval_integrable; continuity,
  all_goals { apply continuous.continuous_on, continuity },
end

/-- The reduction formula for the integral of `cos x ^ n` for any natural `n ≥ 2`. -/
lemma integral_cos_pow :
  ∫ x in a..b, cos x ^ (n + 2) = (cos b ^ (n + 1) * sin b - cos a ^ (n + 1) * sin a) / (n + 2)
    + (n + 1) / (n + 2) * ∫ x in a..b, cos x ^ n :=
begin
  have : (n : ℝ) + 2 ≠ 0 := by exact_mod_cast succ_ne_zero n.succ,
  field_simp,
  convert eq_sub_iff_add_eq.mp (integral_cos_pow_aux n),
  ring,
end

@[simp]
lemma integral_cos_sq : ∫ x in a..b, cos x ^ 2 = (cos b * sin b - cos a * sin a + b - a) / 2 :=
by field_simp [integral_cos_pow, add_sub_assoc]


-- Limite_de_la_suma_de_sucesiones_convergentes.lean
-- Límite de la suma de sucesiones convergentes
-- José A. Alonso Jiménez
-- Sevilla, 14 de julio de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- En Lean, una sucesión u₀, u₁, u₂, ... se puede representar mediante
-- una función (u : ℕ → ℝ) de forma que u(n) es uₙ.
--
-- Se define que a es el límite de la sucesión u, por
--    def limite : (ℕ → ℝ) → ℝ → Prop :=
--    λ u a, ∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - a| < ε
-- donde se usa la notación |x| para el valor absoluto de x
--    notation `|`x`|` := abs x
--
-- Demostrar que el límite de la suma de dos sucesiones convergentes es
-- la suma de los límites de dichas sucesiones.
-- ---------------------------------------------------------------------

import data.real.basic

variables (u v : ℕ → ℝ)
variables (a b : ℝ)

notation `|`x`|` := abs x

def limite : (ℕ → ℝ) → ℝ → Prop :=
λ u c, ∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - c| < ε

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

example
  (hu : limite u a)
  (hv : limite v b)
  : limite (u + v) (a + b) :=
begin
  intros ε hε,
  have hε2 : 0 < ε / 2,
    { linarith },
  cases hu (ε / 2) hε2 with Nu hNu,
  cases hv (ε / 2) hε2 with Nv hNv,
  clear hu hv hε2 hε,
  use max Nu Nv,
  intros n hn,
  have hn₁ : n ≥ Nu,
    { exact le_of_max_le_left hn },
  specialize hNu n hn₁,
  have hn₂ : n ≥ Nv,
    { exact le_of_max_le_right hn },
  specialize hNv n hn₂,
  clear hn hn₁ hn₂ Nu Nv,
  calc |(u + v) n - (a + b)|
       = |(u n + v n) - (a + b)|  : by refl
   ... = |(u n - a) + (v n -  b)| : by {congr, ring}
   ... ≤ |u n - a| + |v n -  b|   : by apply abs_add
   ... < ε / 2 + ε / 2            : by linarith
   ... = ε                        : by apply add_halves,
end

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

example
  (hu : limite u a)
  (hv : limite v b)
  : limite (u + v) (a + b) :=
begin
  intros ε hε,
  cases hu (ε/2) (by linarith) with Nu hNu,
  cases hv (ε/2) (by linarith) with Nv hNv,
  use max Nu Nv,
  intros n hn,
  have hn₁ : n ≥ Nu := le_of_max_le_left hn,
  specialize hNu n hn₁,
  have hn₂ : n ≥ Nv := le_of_max_le_right hn,
  specialize hNv n hn₂,
  calc |(u + v) n - (a + b)|
       = |(u n + v n) - (a + b)|  : by refl
   ... = |(u n - a) + (v n -  b)| : by {congr, ring}
   ... ≤ |u n - a| + |v n -  b|   : by apply abs_add
   ... < ε / 2 + ε / 2            : by linarith
   ... = ε                        : by apply add_halves,
end

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

lemma max_ge_iff
  {α : Type*}
  [linear_order α]
  {p q r : α}
  : r ≥ max p q  ↔ r ≥ p ∧ r ≥ q :=
max_le_iff

example
  (hu : limite u a)
  (hv : limite v b)
  : limite (u + v) (a + b) :=
begin
  intros ε hε,
  cases hu (ε/2) (by linarith) with Nu hNu,
  cases hv (ε/2) (by linarith) with Nv hNv,
  use max Nu Nv,
  intros n hn,
  cases max_ge_iff.mp hn with hn₁ hn₂,
  have cota₁ : |u n - a| < ε/2 := hNu n hn₁,
  have cota₂ : |v n - b| < ε/2 := hNv n hn₂,
  calc |(u + v) n - (a + b)|
       = |u n + v n - (a + b)|   : by refl
   ... = |(u n - a) + (v n - b)| : by { congr, ring }
   ... ≤ |u n - a| + |v n - b|   : by apply abs_add
   ... < ε                       : by linarith,
end

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

example
  (hu : limite u a)
  (hv : limite v b)
  : limite (u + v) (a + b) :=
begin
  intros ε hε,
  cases hu (ε/2) (by linarith) with Nu hNu,
  cases hv (ε/2) (by linarith) with Nv hNv,
  use max Nu Nv,
  intros n hn,
  cases max_ge_iff.mp hn with hn₁ hn₂,
  calc |(u + v) n - (a + b)|
       = |u n + v n - (a + b)|   : by refl
   ... = |(u n - a) + (v n - b)| : by { congr, ring }
   ... ≤ |u n - a| + |v n - b|   : by apply abs_add
   ... < ε/2 + ε/2               : add_lt_add (hNu n hn₁) (hNv n hn₂)
   ... = ε                       : by simp
end

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

example
  (hu : limite u a)
  (hv : limite v b)
  : limite (u + v) (a + b) :=
begin
  intros ε hε,
  cases hu (ε/2) (by linarith) with Nu hNu,
  cases hv (ε/2) (by linarith) with Nv hNv,
  use max Nu Nv,
  intros n hn,
  rw max_ge_iff at hn,
  calc |(u + v) n - (a + b)|
       = |u n + v n - (a + b)|   : by refl
   ... = |(u n - a) + (v n - b)| : by { congr, ring }
   ... ≤ |u n - a| + |v n - b|   : by apply abs_add
   ... < ε                       : by linarith [hNu n (by linarith), hNv n (by linarith)],
end

-- 6ª demostración
-- ===============

example
  (hu : limite u a)
  (hv : limite v b)
  : limite (u + v) (a + b) :=
begin
  intros ε Hε,
  cases hu (ε/2) (by linarith) with L HL,
  cases hv (ε/2) (by linarith) with M HM,
  set N := max L M with hN,
  use N,
  have HLN : N ≥ L := le_max_left _ _,
  have HMN : N ≥ M := le_max_right _ _,
  intros n Hn,
  have H3 : |u n - a| < ε/2 := HL n (by linarith),
  have H4 : |v n - b| < ε/2 := HM n (by linarith),
  calc |(u + v) n - (a + b)|
       = |(u n + v n) - (a + b)|   : by refl
   ... = |(u n - a) + (v n - b)|   : by { congr, ring }
   ... ≤ |(u n - a)| + |(v n - b)| : by apply abs_add
   ... < ε/2 + ε/2                 : by linarith
   ... = ε                         : by ring
end


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

! This file was ported from Lean 3 source module number_theory.l_series
! leanprover-community/mathlib commit 32253a1a1071173b33dc7d6a218cf722c6feb514
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.NormedSpace.FiniteDimension
import Mathbin.Analysis.PSeries
import Mathbin.NumberTheory.ArithmeticFunction
import Mathbin.Topology.Algebra.InfiniteSum.Basic

/-!
# L-series

Given an arithmetic function, we define the corresponding L-series.

## Main Definitions
 * `nat.arithmetic_function.l_series` is the `l_series` with a given arithmetic function as its
  coefficients. This is not the analytic continuation, just the infinite series.
 * `nat.arithmetic_function.l_series_summable` indicates that the `l_series`
  converges at a given point.

## Main Results
 * `nat.arithmetic_function.l_series_summable_of_bounded_of_one_lt_re`: the `l_series` of a bounded
  arithmetic function converges when `1 < z.re`.
 * `nat.arithmetic_function.zeta_l_series_summable_iff_one_lt_re`: the `l_series` of `ζ`
  (whose analytic continuation is the Riemann ζ) converges iff `1 < z.re`.

-/

noncomputable section

open BigOperators

namespace Nat

namespace ArithmeticFunction

/-- The L-series of an `arithmetic_function`. -/
def lSeries (f : ArithmeticFunction ℂ) (z : ℂ) : ℂ :=
  ∑' n, f n / n ^ z
#align nat.arithmetic_function.l_series Nat.ArithmeticFunction.lSeries

/-- `f.l_series_summable z` indicates that the L-series of `f` converges at `z`. -/
def LSeriesSummable (f : ArithmeticFunction ℂ) (z : ℂ) : Prop :=
  Summable fun n => f n / n ^ z
#align nat.arithmetic_function.l_series_summable Nat.ArithmeticFunction.LSeriesSummable

theorem lSeries_eq_zero_of_not_lSeriesSummable (f : ArithmeticFunction ℂ) (z : ℂ) :
    ¬f.LSeriesSummable z → f.lSeries z = 0 :=
  tsum_eq_zero_of_not_summable
#align nat.arithmetic_function.l_series_eq_zero_of_not_l_series_summable Nat.ArithmeticFunction.lSeries_eq_zero_of_not_lSeriesSummable

@[simp]
theorem lSeriesSummable_zero {z : ℂ} : LSeriesSummable 0 z := by
  simp [l_series_summable, summable_zero]
#align nat.arithmetic_function.l_series_summable_zero Nat.ArithmeticFunction.lSeriesSummable_zero

theorem lSeriesSummable_of_bounded_of_one_lt_real {f : ArithmeticFunction ℂ} {m : ℝ}
    (h : ∀ n : ℕ, Complex.abs (f n) ≤ m) {z : ℝ} (hz : 1 < z) : f.LSeriesSummable z :=
  by
  by_cases h0 : m = 0
  · subst h0
    have hf : f = 0 :=
      arithmetic_function.ext fun n =>
        complex.abs.eq_zero.1 (le_antisymm (h n) (complex.abs.nonneg _))
    simp [hf]
  refine' summable_of_norm_bounded (fun n : ℕ => m / n ^ z) _ _
  · simp_rw [div_eq_mul_inv]
    exact (summable_mul_left_iff h0).2 (Real.summable_nat_rpow_inv.2 hz)
  · intro n
    have hm : 0 ≤ m := le_trans (complex.abs.nonneg _) (h 0)
    cases n
    · simp [hm, Real.zero_rpow (ne_of_gt (lt_trans Real.zero_lt_one hz))]
    simp only [map_div₀, Complex.norm_eq_abs]
    apply div_le_div hm (h _) (Real.rpow_pos_of_pos (Nat.cast_pos.2 n.succ_pos) _) (le_of_eq _)
    rw [Complex.abs_cpow_real, Complex.abs_cast_nat]
#align nat.arithmetic_function.l_series_summable_of_bounded_of_one_lt_real Nat.ArithmeticFunction.lSeriesSummable_of_bounded_of_one_lt_real

theorem lSeriesSummable_iff_of_re_eq_re {f : ArithmeticFunction ℂ} {w z : ℂ} (h : w.re = z.re) :
    f.LSeriesSummable w ↔ f.LSeriesSummable z :=
  by
  suffices h :
    ∀ n : ℕ, Complex.abs (f n) / Complex.abs (↑n ^ w) = Complex.abs (f n) / Complex.abs (↑n ^ z)
  · simp [l_series_summable, ← summable_norm_iff, h, Complex.norm_eq_abs]
  intro n
  cases n
  · simp
  apply congr rfl
  have h0 : (n.succ : ℂ) ≠ 0 := by
    rw [Ne.def, Nat.cast_eq_zero]
    apply n.succ_ne_zero
  rw [Complex.cpow_def, Complex.cpow_def, if_neg h0, if_neg h0, Complex.abs_exp_eq_iff_re_eq]
  simp only [h, Complex.mul_re, mul_eq_mul_left_iff, sub_right_inj]
  right
  rw [Complex.log_im, ← Complex.ofReal_nat_cast]
  exact Complex.arg_of_real_of_nonneg (le_of_lt (cast_pos.2 n.succ_pos))
#align nat.arithmetic_function.l_series_summable_iff_of_re_eq_re Nat.ArithmeticFunction.lSeriesSummable_iff_of_re_eq_re

theorem lSeriesSummable_of_bounded_of_one_lt_re {f : ArithmeticFunction ℂ} {m : ℝ}
    (h : ∀ n : ℕ, Complex.abs (f n) ≤ m) {z : ℂ} (hz : 1 < z.re) : f.LSeriesSummable z :=
  by
  rw [← l_series_summable_iff_of_re_eq_re (Complex.ofReal_re z.re)]
  apply l_series_summable_of_bounded_of_one_lt_real h
  exact hz
#align nat.arithmetic_function.l_series_summable_of_bounded_of_one_lt_re Nat.ArithmeticFunction.lSeriesSummable_of_bounded_of_one_lt_re

open ArithmeticFunction

theorem zeta_lSeriesSummable_iff_one_lt_re {z : ℂ} : LSeriesSummable ζ z ↔ 1 < z.re :=
  by
  rw [← l_series_summable_iff_of_re_eq_re (Complex.ofReal_re z.re), l_series_summable, ←
    summable_norm_iff, ← Real.summable_one_div_nat_rpow, iff_iff_eq]
  by_cases h0 : z.re = 0
  · rw [h0, ← summable_nat_add_iff 1]
    swap
    · infer_instance
    apply congr rfl
    ext n
    simp [n.succ_ne_zero]
  · apply congr rfl
    ext ⟨- | n⟩
    · simp [h0]
    simp only [cast_zero, nat_coe_apply, zeta_apply, succ_ne_zero, if_false, cast_succ, one_div,
      Complex.norm_eq_abs, map_inv₀, Complex.abs_cpow_real, inv_inj, zero_add]
    rw [← cast_one, ← cast_add, Complex.abs_of_nat, cast_add, cast_one]
#align nat.arithmetic_function.zeta_l_series_summable_iff_one_lt_re Nat.ArithmeticFunction.zeta_lSeriesSummable_iff_one_lt_re

@[simp]
theorem lSeries_add {f g : ArithmeticFunction ℂ} {z : ℂ} (hf : f.LSeriesSummable z)
    (hg : g.LSeriesSummable z) : (f + g).lSeries z = f.lSeries z + g.lSeries z :=
  by
  simp only [l_series, add_apply]
  rw [← tsum_add hf hg]
  apply congr rfl (funext fun n => _)
  apply _root_.add_div
#align nat.arithmetic_function.l_series_add Nat.ArithmeticFunction.lSeries_add

end ArithmeticFunction

end Nat


/- Copyright (c) 2022 Sina Hazratpour. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
----------------

# Logic of Propositions
Sina Hazratpour
Introduction to Proof
MATH 301, Johns Hopkins University, Fall 2022
-/

/- # New tactics unveiled in this lesson:
12. `split` (for introduction of `∧`)
13. `cases` (for elimination of `∧`)
14. `assumption` (for using a proof from context of assumptions)
15. `intro` (for introduction of `→`)
16. `apply` (for backward elimination of `→`)
17. `have` (for introducing auxiliary intermediate goals)
18. `left` (for introduction of `∨`)
19. `right` (for introduction of `∨`)
20. `suffices`
-/

/- # Goals of this lecture:
1. Identify techniques for proving a proposition based on its logical structure.
2. Recognize and apply standard proof techniques, including proof by negation, proof by contraposition, and proof by contradiction.
3. Learn to simplify your proofs.
4. Do all of the above using interactive Lean tactics.
-/

import ..prooflab
--import .arithmetic_with_tactics
import .lec5_function_composition -- importing is transitive, hence we have already imported proofs1 and proofs0.

set_option pp.beta true
set_option pp.generalized_field_notation false
-- set_option pp.all true

namespace PROOFS

/-! ## Propositions
Reminder: We declare a proposition by `P : Prop`.
For `P : Prop`, we read `hp : P` as "hp is a proof of P", or "the hypothesis P verified by hp".
-/

/-
We introduce global variables; in below P and Q are accessible throughout the entire file, and any file which imports this file.
-/
variables P Q R : Prop -- let `P`, `Q` and `R` be propositions.

/-
Lean's "check" command tells us
the type of the object we have constructd.
-/
#check P
#check P ∧ Q -- conjunction
#check P ∨ Q -- disjunction
#check P → Q -- implication
#check ¬ P -- negation

/-! ## New Propositions from the Old

Using simple (aka __atomic__) propositions such as “The sun is shining” and “It is raining” we can form the more complicated proposition “The sun is shining and It is raining”.

That required us to first unquote the sentences, insert an “and” and then put a quote around the resulting sentence.

In this way, we can make new propositions from old propositions using __connectives__ (such as “and”/conjunction, "if ... then"/implication, "or"/disjunction). For each connective, we specify a __rule__ to __introduce__ a proof of the compound proposition using that connective (the so-called introduction rule) and a rule to __use__ or ___eliminate__ such a proof.

We call a compound proposition (such as “The sun is shining and It is raining”) a __propositional formula__.
-/

/-! ### Conjunction (and)
We use the tactic __split__ in order to prove a proposition formula of the form `P ∧ Q`.

    P   Q
   -------- ∧-intro
    P ∧ Q

We use the tactic __cases__ in order to use a proof of `P ∧ Q` to prove some other proposition. The tactic `cases` replaces `h : P ∧ Q` by a pair of assumptions, `hp : P` and `hq : Q`.

    P ∧ Q
   -------- ∧-elim_left
      P

    P ∧ Q
   -------- ∧-elim_right
      Q

-/

-- introduction example
example (hp : P) : P ∧ P :=
begin
  split,
  {
    -- the proof of the first subgoal which is `P`
    exact hp,
  },
  {
    -- the proof of the second subgoal which is `P`
    exact hp,
  },
end

example (hp : P) : P ∧ P :=
begin
  split,
  repeat {exact hp},
end

-- elimination example
lemma fst_prop (h : P ∧ Q) :
  P :=
begin
  cases h,
  exact h_left,
end

-- elimination example
lemma fst_prop_alt (h : P ∧ Q) :
  P :=
begin
  cases h with h₁ h₂,
  exact h₁,
end

--elimination example
lemma snd_prop (h : P ∧ Q) :
  Q :=
begin
  cases h with h₁ h₂,
  exact h₂,
end

/- the following is an example of both intro and elim rules: we need intro rule to construct a proof of `Q ∧ P` and we need elim rule to be able to use `h : P ∧ Q` -/
lemma conjunction_swap (h : P ∧ Q) :
  Q ∧ P :=
begin
  split,
  {
    -- we need to prove `Q`.
    -- we are trying to use `h` to prove `Q` holds.
    cases h with sheep goat,
    exact goat,
  },
  {
   -- we need to prove `P`.
    -- we are trying to use `h` to prove `Q` holds.
    cases h with sheep goat,
    exact sheep,
  },
end

#check conjunction_swap

lemma conjunction_swap_alt (h : P ∧ Q) :
  Q ∧ P :=
begin
  cases h with hp hq, -- eliminating h to get proofs of `P` and `Q`.
  split,  -- we want to introduce a proof of conjunction
  {
    exact hq,
  },
  {
    exact hp,
  },
end

------------------------
-- tactic __assumption__
------------------------
/-
The `assumption` tactic looks through the assumptions in the context of the current goal, and if there is one matching the conclusion, it applies that asssumption. Here is an example:
-/

/-
This one is the same as the last proof but more parsimonous: we use `repeat {assumption}` instead of many instances of `exact`.
-/

lemma conjunction_swap_alt_alt (h : P ∧ Q) :
  Q ∧ P :=
begin
  cases h with hp hq, -- eliminating h to get proofs of `P` and `Q`.
  split,
  {
    assumption, -- this tactic means you are instructing Lean to go and find the proof of the goal from the context of assumption, only if the exact proof exists in the context.
  },
  {
    assumption,
  },
end

lemma conjunction_swap_alt_alt_alt (h : P ∧ Q) :
  Q ∧ P :=
begin
  cases h with hp hq, -- eliminating h to get proofs of `P` and `Q`.
  split,
  repeat {assumption},
end

lemma conjunction_swap_alt_alt_alt_alt (h : P ∧ Q) :
  Q ∧ P :=
begin
  split,
  repeat { cases h, assumption},
end

-- introduction example : complete the proof below using only `assumption` and `exact` tactics.
example (hp : P) (hq : Q) (hr : R) :
  (P ∧ Q) ∧ (P ∧ R) :=
begin
  split,
  {
    split,
    {
      assumption, -- or exact hp
    },
    {
      assumption,
    },
  },
  {
    split,
    {
      assumption,
    },
    {
      assumption,
    },
  },
end

-- Challenge: make it shorter with `repeat`.

example (hp : P) (hq : Q) (hr : R) :
  (P ∧ Q) ∧ (P ∧ R) :=
begin
  split,
  {
    split,
      repeat {assumption},
  },
  {
    split,
    repeat {assumption},
  },
end

/-! ### Implication (If ... then ...)
The implication `P → Q` is a new proposition built from P and Q and we read it as “if P then Q”.

Implicaiton __introduction__:
If under the assumption that `P` is true we can derive that `Q` is true, then we know that `P → Q` is true. Note that, in this process, we actually do not know whether P is true or not; all we know is that in case `P` holds, then so does `Q`. To assume `P` first we introduce a hypothetical proof `hp` of `P`, and we use `hp` to construct a proof of `Q`, and thereby show that `Q` holds under the assumption that `P` holds.

    P
    .
    .
    .
    Q
--------- (→ intro)
  P → Q

Implication __elimination__:
Application of a proof
`h : P → Q`
to a proof
`p : P`
is achieved by the expression
`h p`
that is `h` followed by `p`.

This is (like) function application.

  P → Q   P
------------- (→ elim)
      Q

If `h` and `p` are compound expressions, we put brackets around them to make it clear where each one begins and ends.
e.g. `h₁ h₂ h₃` is interpreted by Lean as `(h₁ h₂) h₃`.
-/

--introduction example
example :
  P → P :=
begin
  intro hp, -- this is the assumption that `P` holds. our goal changes
  exact hp,
end

example :
false → false :=
begin
  intro hp,
  exact hp,
end

example :
  false → true :=
begin
-- an example of this is the proposition (0 = 1) → (1 = 1)
intro hp,
exact trivial, -- trivial is the unique proof of proposition `true`, therefore `true` is true.
end

--introduction example
lemma conjunction_commutative :
  P ∧ Q → Q ∧ P :=
begin
  intro hpq,
  -- we want to use the elim rule of conjunction (`∧`) to construct a proof of `P` and a proof of `Q` to use them later.
  cases hpq with hp hq,
  -- we want to use the intro rule of conjunction to prove our goal `Q ∧ P`
  split,
  {
    -- well, we know that `Q` is true and the proof of that is `hq`.
    exact hq,
  },
  {-- well, we know that `P` is true and the proof of that is `hp`.
    assumption, -- note that `hp` was introduced later in the proof
  },
end

--Alternatively, we can use the lemma `conjunction_swap` which we proved before:
example :
  P ∧ Q → Q ∧ P :=
begin
-- the second line of the following proof uses the elimination rule of implication which we have not learned yet, but we will in about five minutes.
  intro hpq,
  exact conjunction_swap P Q hpq,
end

 -- an example of implication introduction
example (n : ℕ) :
  (0 = 1) → (0 = n) :=
begin
intro h₁,
calc 0 = 0 * n : by rw zero_mul
... = 1 * n : by rw h₁
... = n : by rw one_mul,
end

/- Next examples constructs a proof of `(0 = 1) → (0 = n)`. -/

lemma nat_mul_congr (x y z : ℕ) (h : x = y) :
  x * z = y * z :=
begin
  rw h,
end

-- #check nat_mul_congr

------------------------
-- tactic __have__
------------------------

/- `have` is a good tactic to use, if you want to add a new intermediate subgoal which, after proving it, can be used later as a new assumption in the (updated) context.

There are two styles of using the tactic `have`.

- The term-style:
` have hp : P, from ...  `

- The tactic-style:
` have hp : P, by {tactic_1 ..., tactic_2 ...,  tactic_n ... } `

In the first style, we are introducing a new assumption `hp` (a proof of proposition `P`) from another proof (`...`) that we know how to construct.
-/

example (n : ℕ) :
  (0 = 1) → (0 = n) :=
begin
  -- we want to prove an implication, hence we use the implication introduction rule.
  intro h₁,
  -- under the hypothetical assumption that `0 = 1` we need to prove that `0 = n`.  To prove this we use the lemma `nat_mul_congr` in the following way: we use the lemma by using x to be `0` and `y` to be `1` and `z` to be `n`
  -- We use the tactic `have` to add a new assumption `h₂` which proves that `0 * n = 1 * n` from the lemma `nat_mul_congr` applied to arguments `0 1 n h₁`.
  have h₂ : 0 * n = 1 * n, from nat_mul_congr 0 1 n h₁,
  -- this proves ` 0 * n = 1 * n`
  -- use h₂ to prove `0 =n`
  rw [zero_mul, one_mul] at h₂,
  assumption,
end

example (n : ℕ) :
  (0 = 1) → (0 = n) :=
begin
  intro h₁,
  -- now under the hypothetical assumption that `0 = 1` we need to prove that `0 = n`.
  have h₂ : 0 * n = 1 * n, from nat_mul_congr 0 1 n h₁,
  -- we add a new assumption that `0 = 0 * n` using the tactic-style `have` and the lemma `zero_lemma`.
  have h₃ : 0 = 0 * n, by {rw zero_mul},
  -- we add a new assumption that `n = 1 * n` using the tactic-style `have` and the lemma `one_mul`.
  have h₄ : n = 1 * n, by {rw one_mul},
  rw ← h₃ at h₂,
  rw ← h₄ at h₂,
  show 0 = n, from h₂,
end

example (n : ℕ) :
  (0 = 1) → (0 = n) :=
begin
intro h₁,
calc 0 = 0 * n : by rw zero_mul
... = 1 * n : by rw h₁
... = n : by rw one_mul,
end

example (n : ℕ) :
  (0 = 1) → (0 = n) :=
begin
  intro h,
  simp at *,
  exfalso,
  assumption,
end

--Alternatively, we can use the lemma `conjunction_swap` which we proved before:
example :
  P ∧ Q → Q ∧ P :=
begin
  intro h₁,
  exact conjunction_swap P Q h₁,
end

example :
  P → Q → P :=
begin
-- we want to prove `P → (Q → P)` so we use the intro rule of `→`
intro h₁,
-- we are proving an implication, hence we use the intro rule of →
intro h₂,
-- we know `P` is true by `h₁`.
assumption,
end

example :
  P → Q → P ∧ Q :=
begin
  sorry,
end

-- Application elimination exmaple aka Modus Ponens
lemma modus_ponens :
  P → (P → Q) → Q :=
begin
  intros hp h,
  exact h hp,
end

-- Depenedent Modus Ponens
lemma dep_modus_ponens:
  P → (P → P → Q) → Q :=
begin
  -- we want to prove P → ((P →(P → Q)) → Q) so we use intro rule of →
  intro h₁,
  -- we want to prove (P →(P → Q))  → Q so we use intro rule of →
  intro h₂,
  --  we want to Q; we create a new subgoal P → Q, and prove it using tactic `have`
  have h₃ : P → Q, from h₂ h₁,
-- we use `h₃` to prove `Q` by application elimination.
  exact h₃ h₁,
end

-- Depenedent Modus Ponens
example :
  P → (P → P → Q) → Q :=
λ h₁, λ h₂, (h₂ h₁) h₁

/-
Transitivity of implications: If we know that  proposition P implies Q and Q implies R then we know that  P implies R.
-/

theorem transitivity_of_implication_1 :
  (P → Q) → (Q → R) → P → R :=
begin
  sorry,
end

/-
Another style to write the same proof above by using the tactic
have ..., from ...
-/

theorem transitivity_of_implication_2 :
  (P → Q) → (Q → R) → P → R :=
begin
  sorry,
end

example :
  (P → (Q → R)) → (P ∧ Q → R) :=
begin
  sorry,
end

------------------------
-- tactic __apply__
------------------------
/- The `apply` tactic takes a proof of a general statement or implication, tries to match the conclusion with the current goal, and leaves the hypotheses, if any, as new goals. If the given proof matches the goal exactly (modulo definitional equality), you can use the exact tactic instead of apply. -/

example (hpq : P → Q) (hqr : Q → R) (hp : P) :
  R :=
-- We prove `R` using backward reasoning as follows.
begin
-- To prove `R`, by hypothesis `hqr` it suffices to prove `Q`.
  apply hqr,
-- To prove `Q`, by hypothesis `hpq` it suffices to prove `P`.
  apply hpq,
-- To prove `P` we use hypothesis `hp`.
  apply hp,
end

section
variables a b c : ℝ
#check (le_refl a : a ≤ a)
#check (le_trans : a ≤ b → b ≤ c → a ≤ c)
#check (le_trans : a ≤ b → b ≤ c → a ≤ c)
end

example (x y z : ℝ) (h₀ : x ≤ y) (h₁ : y ≤ z) :
  x ≤ z :=
begin
exact le_trans h₀ h₁,
end

example (x y z : ℝ) (h₀ : x ≤ y) (h₁ : y ≤ z) :
  x ≤ z :=
begin
-- our goal is to prove `x ≤ z`. After using `apply le_trans` Lean is searching the context to find some `y` with `x ≤ y` and `y ≤ z` so that it can apply `le_trans` to them.
  apply le_trans,
-- the goals are ⊢ x ≤ ?m_1 and ?m_1 ≤ z : We solve the first goal using `h₀`. One `h₀` is supplied, `?m_1` is replaced by `y`.
  apply h₀,
-- ⊢ the only remaining goal is  `y ≤ z`.
  apply h₁,
-- alternatively, we could have used `exact h₁` as usual.
end

/-
let's do curry fully tactic style; we use our new tactic `apply`.
-/
-- a tautology is proposition which is always true.
lemma curry_prop :
  (P ∧ Q → R) → (P → Q → R) :=
begin
  intro h₁, -- we want to prove the implication (P ∧ Q → R) → (P → Q → R) hence we use the introduction rule of implication.
  intro h₂, -- we want to prove the implication P → (Q → R), hence we use the introduction rule of implication.
  intro h₃,  -- we want to prove Q → R, hence we use the introduction rule of implication.
  apply h₁, -- we want to show R hence by backward proving we need to supply a proof of P ∧ Q
  split, -- we want to prove P ∧ Q and therefore we use the introduction rule of conjunction
  repeat {assumption},
end

example :
  (P ∧ Q → R) → (P → Q → R) :=
begin
itauto,
end

example :
  (P ∧ Q → R) → (P → Q → R) :=
begin
itauto,
end

example :
  (P ∧ Q → R) → (P → Q → R) :=
begin
exact and_imp.mp,
end

#check curry_prop
#check curry

lemma curry_prop_fun :
  (P ∧ Q → R) → (P → Q → R) :=
λ h, λ hp, λ hq, h ⟨hp, hq⟩

#check curry_prop_fun

example :
  (P → (Q → R)) → (P ∧ Q → R) :=
begin
  sorry,
end

/-
The proof below shows us why we prefer backward reasoning with `apply`. A forward reasoning proof would be more complicated
-/

example : (((P → Q)  → Q) → Q) → P → Q :=
begin
  sorry,
end

/-! ### Biimplication, or otherwise known as If and Only If
The biimplication ` ↔ ` is a derived connective which is derived form `→` and `∧`. For propositions `P` and `Q`, we write `P ↔ Q` for `(P → Q) ∧ (Q → P)`. Therefore `P ↔ Q = (P → Q) ∧ (Q → P)` by definition. And, as such, we can apply the tactic `split` to introduce a proof of `P ↔ Q` and `cases` to eliminate proofs of `P ↔ Q`.
-/

/-
**Note** The `constructor` tactic applies the unique constructor for conjunction, namely `and.intro`.
-/

lemma conj_comm : P ∧ Q ↔ Q ∧ P :=
begin
  split,
  {
    sorry,
  },
  {
    sorry,
  },
end

example :
P ∧ Q ∧ R → R ∧ Q ∧ P :=
begin
    sorry,
end

lemma disj_comm {P Q : Prop} :
  P ∨ Q ↔ Q ∨ P :=
begin
  sorry,
end

#check disj_comm
#check disj_comm.mp -- extract the forward direction implication `P ∨ Q → Q ∨ P` from the proof `disj_comm` by using `.mp` notation.
#check disj_comm.mpr -- extract the converse implication `Q ∨ P → P ∨ Q` from the proof `disj_comm` by using `.mpr` notation.

theorem conjunction_distributes_over_disjunction :
  P ∧ (Q ∨ R) ↔ (P ∧ Q) ∨ (P ∧ R) :=
begin
  sorry,
end

#check conjunction_distributes_over_disjunction
#check (conjunction_distributes_over_disjunction P Q R).mp
#check (conjunction_distributes_over_disjunction P Q R).mpr

#check eq_zero_or_eq_zero_of_mul_eq_zero

example (a b : ℝ) :
  a * b = 0 ↔ (a = 0) ∨ (b = 0) :=
begin
  sorry,
end

/-
We say two propositions `P` and `Q` are __equivalent__ if we can give a proof of `P ↔ Q`.
-/

/-
We prove that if propositions `P` and `Q` are equivalent then `A ∧ P` and `A ∧ Q` are equivalent for any proposition `A`.
-/

lemma conj_respects_eqv {A P Q : Prop} :
  A ∧ P ↔ A ∧ Q :=
begin
  sorry,
end

-- Todo: prove similar lemmas for disjunction, implication, and negation

lemma disj_respects_eqv {A P Q : Prop} :
  A ∨ P ↔ A ∨ Q :=
begin
  sorry,
end

def is_eqv (P Q : Prop) := P ↔ Q

#check is_eqv

example {A B : Prop} (hPQ : is_eqv P Q) :
  A ∧ (P ∨ B) ↔ A ∧ (Q ∨ B) :=
begin
  sorry,
end
-- What is the general statement here?

#check and
#check and P Q

def and_curry : Prop × Prop → Prop :=
λ α , (α.1 ∧ α.2)

#check copy
#reduce copy

example :
  is_eqv (and_curry (copy P)) P :=
begin
unfold copy,
unfold and_curry,
dsimp,
split,
  {
    intro h,
    cases h with h₁ h₂,
    exact h₁,
  },
  {
    intro h,
    split,
    assumption',
  },
end

#check is_eqv ∘ and_curry ∘ copy

-- We say that two functions `f: Prop → Prop` and `g: Prop → Prop` are pointwise equivalent if for any propostion `P`

def is_eqv_ptw (P : Prop) (f g : Prop → Prop) :=
is_eqv (f P) (g P)

#check is_eqv_ptw

/-! ### Falsity
Sometimes we need to work with propositions which are always false such as “A bachelor is married”, or "0 = 1". In Lean, we denote the false proposition by `false`. The universal property of a false proposition is that any other proposition follows from the false proposition. We can prove any proposition from `false`. This is known as the __principle of explosion__ 🎆 aka ex falso.
-/

example (P : Prop) (h : false) :
  P :=
begin
  exfalso,
  exact h,
end

example :
  false → P :=
begin
  intro h,
  exfalso,
  exact h,
end

/-! ### True
Sometimes we need to work with propositions which are always true such as “A bachelor is unmarried”, or "0 = 0". In Lean, we denote the false proposition by `true`. The universal property of a false proposition is that any other proposition entails the true proposition. In fact, `true` has a unique proof, namely `trivial`.
We can prove any proposition from `false`. This is known as the __principle of explosion__ 🎆 aka ex falso.
-/

example :
  true :=
begin
  exact trivial,
end

example : false → true → Q → P → Q   :=
begin
intro u,
exfalso,
exact u,
end

/-! ### Negation
If we start with a propositon `P`, the negation `¬P` (aka "not P") is _defined_ by the formula `P → false`, which you can think of as saying that `P` implies something impossible (`false`). Therefore, if `¬ P` is the case, then `P` cannot be the case, since if `P` were the case, we would conclude that something false/impossible would be the case. The rules for negation are therefore similar to the rules for implication. To prove/introduce `¬P`, assume `P` and derive a contradiction `false` (i.e. construct a proof of proposition `false`).  An example of this is the proof of irrationality of root 2.
To eliminate `¬P`, given a proof of `P` and a proof of `¬ P` we get `false`.
-/

-- We are trying to prove that "if P then if not P then false"
example :
  P → ¬P → false :=
begin
  intro hp, -- we want to prove the implication P → (¬P → false), therefore we use implication introduction
  intro hnp, -- we want to prove the implication (¬P) → false, therefore we use implication introduction
  apply hnp,          -- we have a proof of ¬P, so we use the elimination for negation to construct a proof of false.
  assumption,
end

example : P → ¬P → false :=
begin
intro hp,
intro hnp,
exact hnp hp, -- the direct form of implication elimination
end

lemma not_imply :
  (P ∧ ¬ Q) → ¬ (P → Q) :=
begin
  intro hpnq, -- we are trying to prove the implication (P ∧ ¬ Q) → (¬ (P → Q))
  -- we want to prove  the negation ¬ (P → Q), so we use the intro rule for negation
  intro hpq,
  cases hpnq with hp hnq,-- we eliminate the conjunction
  apply hnq, -- we use the elim rule for `¬ Q` to change the goal from false to Q
  apply hpq, --we use the elim rule for `P → Q` after which we just need to prove `P`.
  assumption,
end

lemma proof_by_contrapositive
  (P Q : Prop) : (P → Q) → (¬Q → ¬P) :=
begin
  intro hpq,
  intro hnq,
  intro hp,
  apply hnq,
  apply hpq,
  assumption,
end

-- Tactic __suffices__:
lemma proof_by_contrapositive_alt
(P Q : Prop) : (P → Q) → (¬Q → ¬P) :=
begin
  intro hpq,
  intro hnq,
  intro hp,
  suffices hq : Q, from hnq hq, -- this is very much like apply; it changes the goal to the assumption of the implication. It says I only need to prove Q becasue once i do that i can use `hnq`
  apply hpq,
  exact hp,
end

-- Another example of `suffices`
example :
  P → (Q ∧ R) → P ∧ Q :=
begin
  intros h₁ h₂ ,
  suffices h₃ : Q, from ⟨h₁,h₃⟩,
  exact h₂.1,
end

-- Tactic __exfalso__:
example :
  P ∧ ¬ P → Q :=
begin
  intro hpnp, -- we want to prove the implication (P ∧ ¬ P) → Q, so we use the intro rule of implication
  cases hpnp with hp hnp,
  exfalso, -- is a tactic for the backward elimination of `false`. This means from a proof of `false` everything followes.
  exact hnp hp,
end

example :
  P ∧ ¬ P → Q :=
begin
  intro hpnp, -- we want to prove the implication (P ∧ ¬ P) → Q, so we use the intro rule of implication
  cases hpnp with hp hnp,
  have f : false, from hnp hp,
  exfalso, -- is a tactic for the backward elimination of `false`. This means from a proof of `false` everything followes.
  assumption,
end

example :
  P ∧ ¬ P → Q :=
begin
  intro hpnp,
  exfalso,
  sorry,
end

/-
A __contradiction__ is a collection of propositions which together lead an absuridty, i.e. a proof of `false`. For instance if we have a proof of a proposition `P` and a proof of `¬ P` then we can prove `false`. Hence `¬ P` contradicts `P`.

Tactic __contradiction__: The `contradiction` tactic searches for a contradiction among the hypotheses of the current goal.
-/

example :
  P ∧ ¬ P → Q :=
begin
  intro h,
  cases h,
  contradiction,
end

/-! ### Disjunction (or)

- Tactic for disjunction introduction:  We use the tactic __left__ or __right__ in order to prove a propositional formula of the form `P ∨ Q`.

      P
   -------- ∨-intro-left
    P ∨ Q

      Q
   -------- ∨-intro-right
     P ∨ Q

- Tactic for disjunction elimination: We use the tactic __cases__ in order to use a proof of `P ∨ Q` to prove some other proposition.

  P ∨ Q     P        Q
            .        .
            .        .
            .        .
            R        R
----------------------------
            R

-/

-- introduction example
example (hp : P) :
  P ∨ Q ∨ ¬ P :=
begin
  left,
  assumption,
end

-- introduction example
example (hq : Q) :
  P ∨ Q ∨ ¬ P :=
begin
  right,
  left,
  assumption,
end

-- introduction example
example (hq : Q) :
  P ∨ Q ∨ ¬ P :=
begin
  itauto,
end

-- Challenge: fill in the `sorry` below.
-- introduction example
example (h : P ∧ Q) :
  P ∨ Q :=
begin
  cases h with hp hq, -- we want to prove P ∨ Q. For this we want to use the elim rule for conjunction.
  -- we want to prove P ∨ Q. For this, we use the left/right intro rule for disjunction.
  left, -- if we know P is true then by the intro rule of disjunction we are done.
  exact hp,
end

-- elimination example
lemma or_swap (h :  P ∨ Q) :
  Q ∨ P :=
begin
  cases h with hp hq, -- we want to prove P ∨ Q. For this we use the elim rule of disjunction.
  {
    right, -- we want to prove Q ∨ P. For this we use the right intro rule of disjunction
    assumption,
  },
  {
    left, -- we want to prove Q ∨ P. For this we use the left intro rule of disjunction
    assumption,
  },
end

/-
The tactic `cases` can be used like `cases h with hp hq` to give customary name to the branches of a disjunction, using `hp` for the first branch and `hq` for the second.
-/

example : P ∨ Q → Q ∨ P :=
begin
  intro h, -- we want to prove an implication, so we use the intro rule of →
  apply or_swap,
  assumption,
end

example : P ∨ Q → Q ∨ P :=
begin
  intro h, -- we want to prove an implication, so we use the intro rule of →
  exact or_swap P Q h,
end

theorem conjunction_distrib_disjunction (h : P ∧ (Q ∨ R) ) :
  (P ∧ Q) ∨ (P ∧ R) :=
begin
   cases h with hp hqr, -- we want to eliminate ∧ in P ∧ (Q ∨ R)
   cases hqr, -- we want to eliminate ∨ in Q ∨ R
   {
    left, -- intro rule for ∨
    split, -- intro rule for ∧
    -- assumption',
    {
      assumption,
    },
    {
      assumption,
    },
   },
  {
   sorry,
  },
end

theorem resolve_left :
  P ∨ Q → ¬ Q → P :=
begin
  intro hpq,
  intro hnq, -- the goal is an implication
  cases hpq with hp hq,
  {
    assumption,
  },
  {
    have f : false, from hnq hq,
    exfalso,
    assumption,
  },
end

theorem resolve_left_alt :
  P ∨ Q → ¬ Q → P :=
begin
  intro hpq,
  intro hnq, -- the goal is an implication
  cases hpq with hp hq,
  {
    assumption,
  },
  {
    exfalso,
    apply hnq,
    exact hq,
  },
end

theorem resolve_left_alt_alt :
  P ∨ Q → ¬ Q → P :=
begin
  intro hpq,
  intro hnq, -- the goal is an implication
  cases hpq with hp hq,
  {
    assumption,
  },
  {
    contradiction, -- use it with care (this tactic is non-constructive!)
  },
end

theorem resolve_right :
  P ∨ Q → ¬ P → Q :=
begin
  sorry,
end

lemma mul_eq_zero_of_eq_zero_or_eq_zero (m n : ℕ) (h : m = 0 ∨ n = 0) :
  m * n = 0 :=
begin
  sorry,
end

#check eq_zero_or_eq_zero_of_mul_eq_zero

example (x : ℤ) (h : x = 1 ∨ x = -1) :
  x^2 = 1 :=
begin
  cases h with h₁ h₂,
  {
    rw h₁,
    ring,
  },
  {
    rw h₂,
    ring,
  },
end

example {x : ℤ} (h : x^2 = 1) : x = 1 ∨ x = -1 :=
begin
  exact sq_eq_one_iff.mp h,
end

section
variable x : ℝ
#check le_or_gt
-- the following lemmas are immediate from the definition of `abs`.
#check (abs_of_nonneg :  0 ≤ x → |x| = x)
#check (abs_of_neg : x < 0 → |x| = -x)
end

lemma lt_abs (x y : ℝ) :
  x < abs y → x < y ∨ x < -y :=
begin
-- Here's our proof strategy:
-- Either `y` is non-negative or it is negative.
--- If `y` is non-negative then `abs y = y` and therefore, our assumption `x < abs y` simplifies to `x < y`.
---- But if `x < y` then `x < y ∨ x < -y`
--- If `y` is negative then `abs y = - y` and therefore, our assumption `x < abs y` simplifies to `x < - y`.
---- But if `x < - y` then `x < y ∨ x < -y`
  cases le_or_gt 0 y, -- since the order ≤  ℝ, ≤) is a linear order
  {
    rw abs_of_nonneg h,
    intro h₁,
    left,
    exact h₁,
  },
  {
    rw abs_of_neg h,
    intro h₂,
    right,
    exact h₂,
  },
end

lemma abs_lt  (x y : ℝ ) :
  abs x < y ↔ - y < x ∧ x < y :=
begin
  sorry,
end

end PROOFS


import Lean.Data.Json
import Mathlib.Data.Nat.Basic
import Mathlib.Init.Algebra.Order
import Mathlib.Init.Data.Nat.Basic
import Mathlib.Init.Data.Nat.Lemmas
import Mathlib.Init.Data.Int.Basic
import Mathlib.Tactic.LibrarySearch
import Mathlib.Data.Equiv.Basic
import Mathlib.Init.Data.Int.Order
import Timelib.Date.Year

open Lean

structure OrdinalDate where
  year : Year
  day : Nat
  hGe : day >= 1
  hLe : day <= year.numDaysInGregorianYear
deriving Repr

instance : ToJson OrdinalDate where
  toJson od := Json.mkObj [("year", ToJson.toJson od.year.val), ("day", od.day)]

instance : FromJson OrdinalDate where
  fromJson? j := do
    let (year : Year) ← (fromJson? (← j.getObjVal? "year")).map Year.mk
    let (day : Nat) ← fromJson? (← j.getObjVal? "day")
    if h : day >= 1 ∧ day <= year.numDaysInGregorianYear
    then return OrdinalDate.mk year day h.left h.right
    else Except.error s!"OrdinalDate day out of range: {day}"

theorem OrdinalDate.eq_of_val_eq : ∀ {o₁ o₂ : OrdinalDate} (h_year : o₁.year = o₂.year) (h_day : o₁.day = o₂.day), o₁ = o₂
| ⟨y₁, d₁, hGt₁, hLt₁⟩, ⟨y₂, d₂, hGt₂, hLt₂⟩, hy, hd => by simp [hy, hd]

instance : Ord OrdinalDate where
  compare d₁ d₂ :=
    match Ord.compare d₁.year d₂.year with
    | Ordering.eq => Ord.compare d₁.day d₂.day
    | owise => owise

instance : LE OrdinalDate := leOfOrd
instance : LT OrdinalDate := ltOfOrd

theorem OrdinalDate.le_def (d₁ d₂ : OrdinalDate) : (d₁ <= d₂) = (compare d₁ d₂).isLE := rfl
theorem OrdinalDate.lt_def (d₁ d₂ : OrdinalDate) : (d₁ < d₂) = (compare d₁ d₂ == Ordering.lt) := rfl

theorem OrdinalDate.le_def' (d₁ d₂ : OrdinalDate) :
  (d₁ <= d₂) ↔ (d₁.year < d₂.year ∨ (d₁.year = d₂.year ∧ d₁.day <= d₂.day)) := by
   simp [OrdinalDate.le_def, Ordering.isLE, compare, compareOfLessAndEq]
   match lt_trichotomy d₁.year d₂.year with
   | .inl y_lt => simp [y_lt]
   | .inr (.inr y_gt) => simp [not_lt_of_gt y_gt, ne_of_gt y_gt]
   | .inr (.inl y_eq) =>
     simp [y_eq, lt_irrefl]
     match lt_trichotomy d₁.day d₂.day with
     | .inl d_lt => simp [d_lt]; exact le_of_lt d_lt
     | .inr (.inr d_gt) => simp [not_lt_of_gt d_gt, ne_of_gt d_gt]; assumption
     | .inr (.inl d_eq) => simp [d_eq, lt_irrefl d₂.day]

theorem OrdinalDate.lt_def' (d₁ d₂ : OrdinalDate) :
  (d₁ < d₂) = (d₁.year < d₂.year ∨ (d₁.year = d₂.year ∧ d₁.day < d₂.day)) := by
   simp [OrdinalDate.lt_def, compare, compareOfLessAndEq]
   match lt_trichotomy d₁.year d₂.year with
   | .inl y_lt => simp [y_lt]
   | .inr (.inr y_gt) => simp [not_lt_of_gt y_gt, ne_of_gt y_gt]
   | .inr (.inl y_eq) =>
     simp [y_eq, lt_irrefl]
     match lt_trichotomy d₁.day d₂.day with
     | .inl d_lt => simp [d_lt]
     | .inr (.inr d_gt) => simp [not_lt_of_gt d_gt, ne_of_gt d_gt]
     | .inr (.inl d_eq) => simp [d_eq, lt_irrefl d₂.day]

instance : LinearOrder OrdinalDate where
  le_refl (a) := by simp [OrdinalDate.le_def']
  le_trans (a b c) := by
    simp only [OrdinalDate.le_def']
    rintro (ay_lt_by | ⟨ay_eq_by, ad_le_bd⟩)
    <;> rintro (by_lt_cy | ⟨by_eq_cy, bd_le_cd⟩)
    . exact .inl $ lt_trans ay_lt_by by_lt_cy
    . exact .inl $ by_eq_cy ▸ ay_lt_by
    . exact .inl $ ay_eq_by ▸ by_lt_cy
    . exact .inr $ ⟨by_eq_cy ▸ ay_eq_by, le_trans ad_le_bd bd_le_cd⟩
  lt_iff_le_not_le (a b) := by
    simp only [OrdinalDate.le_def', OrdinalDate.lt_def']
    refine Iff.intro ?mp ?mpr
    case mp =>
      rintro (y_lt | ⟨y_eq, d_lt⟩)
      case inl =>
        have hr : ¬(b.year < a.year ∨ b.year = a.year ∧ b.day ≤ a.day) :=
          fun
          | .inl y_lt' => absurd y_lt (lt_asymm y_lt')
          | .inr ⟨by_eq_ay, bd_le_ad⟩ => absurd (by_eq_ay ▸ y_lt : a.year < a.year) (lt_irrefl _)
        exact And.intro (Or.inl y_lt) hr
      case inr.intro =>
        have hr : ¬(b.year < a.year ∨ b.year = a.year ∧ b.day ≤ a.day) :=
          fun
          | .inl y_lt' => absurd (y_eq ▸ y_lt' : a.year < a.year) (lt_irrefl _)
          | .inr ⟨by_eq_ay, bd_le_ad⟩ => absurd bd_le_ad (not_le_of_gt d_lt)
        exact And.intro (Or.inr ⟨y_eq, le_of_lt d_lt⟩) hr
    case mpr =>
      rintro ⟨y_lt | ⟨y_eq, d_le⟩, hr⟩
      case intro.inl => exact Or.inl y_lt
      case intro.inr.intro =>
        refine .inr ⟨y_eq, ?x⟩
        match lt_or_eq_of_le d_le with
        | .inl d_lt => exact d_lt
        | .inr d_eq => exact False.elim <| hr <| Or.inr ⟨y_eq.symm, le_of_eq d_eq.symm⟩
  le_antisymm (a b) := by
    simp [OrdinalDate.le_def']
    rintro (ay_lt_by | ⟨ay_eq_by, ad_le_bd⟩)
    <;> rintro (by_lt_ay | ⟨by_eq_ay, bd_le_ad⟩)
    . exact absurd ay_lt_by (not_lt_of_gt by_lt_ay)
    . rw [by_eq_ay] at ay_lt_by; exact absurd (ay_lt_by) (lt_irrefl _)
    . rw [ay_eq_by] at by_lt_ay; exact absurd (by_lt_ay) (lt_irrefl _)
    . exact OrdinalDate.eq_of_val_eq ay_eq_by (le_antisymm ad_le_bd bd_le_ad)
  le_total (a b) := by
    simp [OrdinalDate.le_def']
    match lt_trichotomy a.year b.year with
    | .inl y_lt => simp [y_lt]
    | .inr (.inr y_gt) => simp [y_gt]
    | .inr (.inl y_eq) =>
      simp [y_eq, lt_irrefl]
      match lt_trichotomy a.day b.day with
      | .inl d_lt => simp [le_of_lt d_lt]
      | .inr (.inr d_gt) => simp [le_of_lt d_gt]
      | .inr (.inl d_eq) => simp [d_eq]
  decidable_le := inferInstance


import game.world4.level2 -- hide
namespace mynat -- hide

/-

# Power World

## Level 3: `pow_one`
-/

/- Lemma
For all naturals $a$, $a ^ 1 = a$.
-/
lemma pow_one (a : mynat) : a ^ (1 : mynat) = a :=
begin [nat_num_game]
  rw one_eq_succ_zero,
  rw pow_succ,
  rw pow_zero,
  rw one_mul,
  refl,

end

end mynat -- hide