/-
Copyright (c) 2020 Damiano Testa. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Damiano Testa
-/
import algebra.big_operators.fin
import data.polynomial.degree.definitions

/-!
# Erase the leading term of a univariate polynomial

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

## Definition

* `erase_lead f`: the polynomial `f - leading term of f`

`erase_lead` serves as reduction step in an induction, shaving off one monomial from a polynomial.
The definition is set up so that it does not mention subtraction in the definition,
and thus works for polynomials over semirings as well as rings.
-/

noncomputable theory
open_locale classical polynomial

open polynomial finset

namespace polynomial

variables {R : Type*} [semiring R] {f : R[X]}

/-- `erase_lead f` for a polynomial `f` is the polynomial obtained by
subtracting from `f` the leading term of `f`. -/
def erase_lead (f : R[X]) : R[X] :=
polynomial.erase f.nat_degree f

section erase_lead

lemma erase_lead_support (f : R[X]) :
  f.erase_lead.support = f.support.erase f.nat_degree :=
by simp only [erase_lead, support_erase]

lemma erase_lead_coeff (i : ℕ) :
  f.erase_lead.coeff i = if i = f.nat_degree then 0 else f.coeff i :=
by simp only [erase_lead, coeff_erase]

@[simp] lemma erase_lead_coeff_nat_degree : f.erase_lead.coeff f.nat_degree = 0 :=
by simp [erase_lead_coeff]

lemma erase_lead_coeff_of_ne (i : ℕ) (hi : i ≠ f.nat_degree) :
  f.erase_lead.coeff i = f.coeff i :=
by simp [erase_lead_coeff, hi]

@[simp] lemma erase_lead_zero : erase_lead (0 : R[X]) = 0 :=
by simp only [erase_lead, erase_zero]

@[simp] lemma erase_lead_add_monomial_nat_degree_leading_coeff (f : R[X]) :
  f.erase_lead + monomial f.nat_degree f.leading_coeff = f :=
(add_comm _ _).trans (f.monomial_add_erase _)

@[simp] lemma erase_lead_add_C_mul_X_pow (f : R[X]) :
  f.erase_lead + (C f.leading_coeff) * X ^ f.nat_degree = f :=
by rw [C_mul_X_pow_eq_monomial, erase_lead_add_monomial_nat_degree_leading_coeff]

@[simp] lemma self_sub_monomial_nat_degree_leading_coeff {R : Type*} [ring R] (f : R[X]) :
  f - monomial f.nat_degree f.leading_coeff = f.erase_lead :=
(eq_sub_iff_add_eq.mpr (erase_lead_add_monomial_nat_degree_leading_coeff f)).symm

@[simp] lemma self_sub_C_mul_X_pow {R : Type*} [ring R] (f : R[X]) :
  f - (C f.leading_coeff) * X ^ f.nat_degree = f.erase_lead :=
by rw [C_mul_X_pow_eq_monomial, self_sub_monomial_nat_degree_leading_coeff]

lemma erase_lead_ne_zero (f0 : 2 ≤ f.support.card) : erase_lead f ≠ 0 :=
begin
  rw [ne, ← card_support_eq_zero, erase_lead_support],
  exact (zero_lt_one.trans_le $ (tsub_le_tsub_right f0 1).trans
    finset.pred_card_le_card_erase).ne.symm
end

lemma lt_nat_degree_of_mem_erase_lead_support {a : ℕ} (h : a ∈ (erase_lead f).support) :
  a < f.nat_degree :=
begin
  rw [erase_lead_support, mem_erase] at h,
  exact (le_nat_degree_of_mem_supp a h.2).lt_of_ne h.1,
end

lemma ne_nat_degree_of_mem_erase_lead_support {a : ℕ} (h : a ∈ (erase_lead f).support) :
  a ≠ f.nat_degree :=
(lt_nat_degree_of_mem_erase_lead_support h).ne

lemma nat_degree_not_mem_erase_lead_support : f.nat_degree ∉ (erase_lead f).support :=
λ h, ne_nat_degree_of_mem_erase_lead_support h rfl

lemma erase_lead_support_card_lt (h : f ≠ 0) : (erase_lead f).support.card < f.support.card :=
begin
  rw erase_lead_support,
  exact card_lt_card (erase_ssubset $ nat_degree_mem_support_of_nonzero h)
end

lemma erase_lead_card_support' {c : ℕ} (fc : f.support.card = c + 1) :
  f.erase_lead.support.card = c :=
erase_lead_card_support fc

@[simp] lemma erase_lead_monomial (i : ℕ) (r : R) :
  erase_lead (monomial i r) = 0 :=
begin
  by_cases hr : r = 0,
  { subst r, simp only [monomial_zero_right, erase_lead_zero] },
  { rw [erase_lead, nat_degree_monomial, if_neg hr, erase_monomial] }
end

@[simp] lemma erase_lead_C (r : R) : erase_lead (C r) = 0 :=
erase_lead_monomial _ _

@[simp] lemma erase_lead_X : erase_lead (X : R[X]) = 0 :=
erase_lead_monomial _ _

@[simp] lemma erase_lead_X_pow (n : ℕ) : erase_lead (X ^ n : R[X]) = 0 :=
by rw [X_pow_eq_monomial, erase_lead_monomial]

@[simp] lemma erase_lead_C_mul_X_pow (r : R) (n : ℕ) : erase_lead (C r * X ^ n) = 0 :=
by rw [C_mul_X_pow_eq_monomial, erase_lead_monomial]

lemma erase_lead_add_of_nat_degree_lt_left {p q : R[X]} (pq : q.nat_degree < p.nat_degree) :
  (p + q).erase_lead = p.erase_lead + q :=
begin
  ext n,
  by_cases nd : n = p.nat_degree,
  { rw [nd, erase_lead_coeff, if_pos (nat_degree_add_eq_left_of_nat_degree_lt pq).symm],
    simpa using (coeff_eq_zero_of_nat_degree_lt pq).symm },
  { rw [erase_lead_coeff, coeff_add, coeff_add, erase_lead_coeff, if_neg, if_neg nd],
    rintro rfl,
    exact nd (nat_degree_add_eq_left_of_nat_degree_lt pq) }
end

lemma erase_lead_add_of_nat_degree_lt_right {p q : R[X]} (pq : p.nat_degree < q.nat_degree) :
  (p + q).erase_lead = p + q.erase_lead :=
begin
  ext n,
  by_cases nd : n = q.nat_degree,
  { rw [nd, erase_lead_coeff, if_pos (nat_degree_add_eq_right_of_nat_degree_lt pq).symm],
    simpa using (coeff_eq_zero_of_nat_degree_lt pq).symm },
  { rw [erase_lead_coeff, coeff_add, coeff_add, erase_lead_coeff, if_neg, if_neg nd],
    rintro rfl,
    exact nd (nat_degree_add_eq_right_of_nat_degree_lt pq) }
end

lemma erase_lead_degree_le : (erase_lead f).degree ≤ f.degree := f.degree_erase_le _

lemma erase_lead_nat_degree_le_aux : (erase_lead f).nat_degree ≤ f.nat_degree :=
nat_degree_le_nat_degree erase_lead_degree_le

lemma erase_lead_nat_degree_lt (f0 : 2 ≤ f.support.card) :
  (erase_lead f).nat_degree < f.nat_degree :=
lt_of_le_of_ne erase_lead_nat_degree_le_aux $ ne_nat_degree_of_mem_erase_lead_support $
  nat_degree_mem_support_of_nonzero $ erase_lead_ne_zero f0

lemma erase_lead_nat_degree_lt_or_erase_lead_eq_zero (f : R[X]) :
  (erase_lead f).nat_degree < f.nat_degree ∨ f.erase_lead = 0 :=
begin
  by_cases h : f.support.card ≤ 1,
  { right,
    rw ← C_mul_X_pow_eq_self h,
    simp },
  { left,
    apply erase_lead_nat_degree_lt (lt_of_not_ge h) }
end

lemma erase_lead_nat_degree_le (f : R[X]) : (erase_lead f).nat_degree ≤ f.nat_degree - 1 :=
begin
  rcases f.erase_lead_nat_degree_lt_or_erase_lead_eq_zero with h | h,
  { exact nat.le_pred_of_lt h },
  { simp only [h, nat_degree_zero, zero_le] }
end

end erase_lead

/-- An induction lemma for polynomials. It takes a natural number `N` as a parameter, that is
required to be at least as big as the `nat_degree` of the polynomial.  This is useful to prove
results where you want to change each term in a polynomial to something else depending on the
`nat_degree` of the polynomial itself and not on the specific `nat_degree` of each term. -/
lemma induction_with_nat_degree_le (P : R[X] → Prop) (N : ℕ)
  (P_0 : P 0)
  (P_C_mul_pow : ∀ n : ℕ, ∀ r : R, r ≠ 0 → n ≤ N → P (C r * X ^ n))
  (P_C_add : ∀ f g : R[X], f.nat_degree < g.nat_degree →
    g.nat_degree ≤ N → P f → P g → P (f + g)) :
  ∀ f : R[X], f.nat_degree ≤ N → P f :=
begin
  intros f df,
  generalize' hd : card f.support = c,
  revert f,
  induction c with c hc,
  { assume f df f0,
    convert P_0,
    simpa only [support_eq_empty, card_eq_zero] using f0 },
  { intros f df f0,
    rw [← erase_lead_add_C_mul_X_pow f],
    cases c,
    { convert P_C_mul_pow f.nat_degree f.leading_coeff _ df,
      { convert zero_add _,
        rw [← card_support_eq_zero, erase_lead_card_support f0] },
      { rw [leading_coeff_ne_zero, ne.def, ← card_support_eq_zero, f0],
        exact zero_ne_one.symm } },
    refine P_C_add f.erase_lead _ _ _ _ _,
    { refine (erase_lead_nat_degree_lt _).trans_le (le_of_eq _),
      { exact (nat.succ_le_succ (nat.succ_le_succ (nat.zero_le _))).trans f0.ge },
      { rw [nat_degree_C_mul_X_pow _ _ (leading_coeff_ne_zero.mpr _)],
        rintro rfl,
        simpa using f0 } },
    { exact (nat_degree_C_mul_X_pow_le f.leading_coeff f.nat_degree).trans df },
    { exact hc _ (erase_lead_nat_degree_le_aux.trans df) (erase_lead_card_support f0) },
    { refine P_C_mul_pow _ _ _ df,
      rw [ne.def, leading_coeff_eq_zero, ← card_support_eq_zero, f0],
      exact nat.succ_ne_zero _ } }
end

/-- Let `φ : R[x] → S[x]` be an additive map, `k : ℕ` a bound, and `fu : ℕ → ℕ` a
"sufficiently monotone" map.  Assume also that
* `φ` maps to `0` all monomials of degree less than `k`,
* `φ` maps each monomial `m` in `R[x]` to a polynomial `φ m` of degree `fu (deg m)`.
Then, `φ` maps each polynomial `p` in `R[x]` to a polynomial of degree `fu (deg p)`. -/
lemma mono_map_nat_degree_eq {S F : Type*} [semiring S]
  [add_monoid_hom_class F R[X] S[X]] {φ : F}
  {p : R[X]} (k : ℕ)
  (fu : ℕ → ℕ) (fu0 : ∀ {n}, n ≤ k → fu n = 0) (fc : ∀ {n m}, k ≤ n → n < m → fu n < fu m)
  (φ_k : ∀ {f : R[X]}, f.nat_degree < k → φ f = 0)
  (φ_mon_nat : ∀ n c, c ≠ 0 → (φ (monomial n c)).nat_degree = fu n) :
  (φ p).nat_degree = fu p.nat_degree :=
begin
  refine induction_with_nat_degree_le (λ p, _ = fu _) p.nat_degree (by simp [fu0]) _ _ _ rfl.le,
  { intros n r r0 np,
    rw [nat_degree_C_mul_X_pow _ _ r0, C_mul_X_pow_eq_monomial, φ_mon_nat _ _ r0] },
  { intros f g fg gp fk gk,
    rw [nat_degree_add_eq_right_of_nat_degree_lt fg, _root_.map_add],
    by_cases FG : k ≤ f.nat_degree,
    { rw [nat_degree_add_eq_right_of_nat_degree_lt, gk],
      rw [fk, gk],
      exact fc FG fg },
    { cases k,
      { exact (FG (nat.zero_le _)).elim },
      { rwa [φ_k (not_le.mp FG), zero_add] } } }
end

lemma map_nat_degree_eq_sub {S F : Type*} [semiring S]
  [add_monoid_hom_class F R[X] S[X]] {φ : F}
  {p : R[X]} {k : ℕ}
  (φ_k : ∀ f : R[X], f.nat_degree < k → φ f = 0)
  (φ_mon : ∀ n c, c ≠ 0 → (φ (monomial n c)).nat_degree = n - k) :
  (φ p).nat_degree = p.nat_degree - k :=
mono_map_nat_degree_eq k (λ j, j - k) (by simp) (λ m n h, (tsub_lt_tsub_iff_right h).mpr) φ_k φ_mon

lemma map_nat_degree_eq_nat_degree {S F : Type*} [semiring S]
  [add_monoid_hom_class F R[X] S[X]] {φ : F} (p)
  (φ_mon_nat : ∀ n c, c ≠ 0 → (φ (monomial n c)).nat_degree = n) :
  (φ p).nat_degree = p.nat_degree :=
(map_nat_degree_eq_sub (λ f h, (nat.not_lt_zero _ h).elim) (by simpa)).trans p.nat_degree.sub_zero

open_locale big_operators

lemma card_support_eq' {n : ℕ} (k : fin n → ℕ) (x : fin n → R) (hk : function.injective k)
  (hx : ∀ i, x i ≠ 0) :  (∑ i, C (x i) * X ^ k i).support.card = n :=
begin
  suffices : (∑ i, C (x i) * X ^ k i).support = image k univ,
  { rw [this, univ.card_image_of_injective hk, card_fin] },
  simp_rw [finset.ext_iff, mem_support_iff, finset_sum_coeff, coeff_C_mul_X_pow,
    mem_image, mem_univ, exists_true_left],
  refine λ i, ⟨λ h, _, _⟩,
  { obtain ⟨j, hj, h⟩ := exists_ne_zero_of_sum_ne_zero h,
    exact ⟨j, (ite_ne_right_iff.mp h).1.symm⟩ },
  { rintros ⟨j, rfl⟩,
    rw [sum_eq_single_of_mem j (mem_univ j), if_pos rfl],
    { exact hx j },
    { exact λ m hm hmj, if_neg (λ h, hmj.symm (hk h)) } },
end

lemma card_support_eq {n : ℕ} : f.support.card = n ↔ ∃ (k : fin n → ℕ) (x : fin n → R)
  (hk : strict_mono k) (hx : ∀ i, x i ≠ 0), f = ∑ i, C (x i) * X ^ k i :=
begin
  refine ⟨_, λ ⟨k, x, hk, hx, hf⟩, hf.symm ▸ card_support_eq' k x hk.injective hx⟩,
  induction n with n hn generalizing f,
  { exact λ hf, ⟨0, 0, is_empty_elim, is_empty_elim, card_support_eq_zero.mp hf⟩ },
  { intro h,
    obtain ⟨k, x, hk, hx, hf⟩ := hn (erase_lead_card_support' h),
    have H : ¬ ∃ k : fin n, k.cast_succ = fin.last n,
    { rintros ⟨i, hi⟩,
      exact (i.cast_succ_lt_last).ne hi },
    refine ⟨function.extend fin.cast_succ k (λ _, f.nat_degree),
      function.extend fin.cast_succ x (λ _, f.leading_coeff), _, _, _⟩,
    { intros i j hij,
      have hi : i ∈ set.range (fin.cast_succ : fin n ↪o fin (n + 1)),
      { rw [fin.range_cast_succ, set.mem_def],
        exact lt_of_lt_of_le hij (nat.lt_succ_iff.mp j.2) },
      obtain ⟨i, rfl⟩ := hi,
      rw fin.cast_succ.injective.extend_apply,
      by_cases hj : ∃ j₀, fin.cast_succ j₀ = j,
      { obtain ⟨j, rfl⟩ := hj,
        rwa [fin.cast_succ.injective.extend_apply, hk.lt_iff_lt,
          ←fin.cast_succ_lt_cast_succ_iff] },
      { rw [function.extend_apply' _ _ _ hj],
        apply lt_nat_degree_of_mem_erase_lead_support,
        rw [mem_support_iff, hf, finset_sum_coeff],
        rw [sum_eq_single, coeff_C_mul, coeff_X_pow_self, mul_one],
        { exact hx i },
        { intros j hj hji,
          rw [coeff_C_mul, coeff_X_pow, if_neg (hk.injective.ne hji.symm), mul_zero] },
        { exact λ hi, (hi (mem_univ i)).elim } } },
    { intro i,
      by_cases hi : ∃ i₀, fin.cast_succ i₀ = i,
      { obtain ⟨i, rfl⟩ := hi,
        rw fin.cast_succ.injective.extend_apply,
        exact hx i },
      { rw [function.extend_apply' _ _ _ hi, ne, leading_coeff_eq_zero,
          ←card_support_eq_zero, h],
        exact n.succ_ne_zero } },
    { rw fin.sum_univ_cast_succ,
      simp only [fin.cast_succ.injective.extend_apply],
      rw [←hf, function.extend_apply', function.extend_apply', erase_lead_add_C_mul_X_pow],
      all_goals { exact H } } },
end

lemma card_support_eq_one : f.support.card = 1 ↔ ∃ (k : ℕ) (x : R) (hx : x ≠ 0), f = C x * X ^ k :=
begin
  refine ⟨λ h, _, _⟩,
  { obtain ⟨k, x, hk, hx, rfl⟩ := card_support_eq.mp h,
    exact ⟨k 0, x 0, hx 0, fin.sum_univ_one _⟩ },
  { rintros ⟨k, x, hx, rfl⟩,
    rw [support_C_mul_X_pow k hx, card_singleton] },
end

lemma card_support_eq_two : f.support.card = 2 ↔ ∃ (k m : ℕ) (hkm : k < m)
  (x y : R) (hx : x ≠ 0) (hy : y ≠ 0), f = C x * X ^ k + C y * X ^ m :=
begin
  refine ⟨λ h, _, _⟩,
  { obtain ⟨k, x, hk, hx, rfl⟩ := card_support_eq.mp h,
    refine ⟨k 0, k 1, hk (by exact nat.zero_lt_one), x 0, x 1, hx 0, hx 1, _⟩,
    rw [fin.sum_univ_cast_succ, fin.sum_univ_one],
    refl },
  { rintros ⟨k, m, hkm, x, y, hx, hy, rfl⟩,
    exact card_support_binomial hkm.ne hx hy },
end

lemma card_support_eq_three : f.support.card = 3 ↔
  ∃ (k m n : ℕ) (hkm : k < m) (hmn : m < n) (x y z : R) (hx : x ≠ 0) (hy : y ≠ 0) (hz : z ≠ 0),
    f = C x * X ^ k + C y * X ^ m + C z * X ^ n :=
begin
  refine ⟨λ h, _, _⟩,
  { obtain ⟨k, x, hk, hx, rfl⟩ := card_support_eq.mp h,
    refine ⟨k 0, k 1, k 2, hk (by exact nat.zero_lt_one), hk (by exact nat.lt_succ_self 1),
      x 0, x 1, x 2, hx 0, hx 1, hx 2, _⟩,
    rw [fin.sum_univ_cast_succ, fin.sum_univ_cast_succ, fin.sum_univ_one],
    refl },
  { rintros ⟨k, m, n, hkm, hmn, x, y, z, hx, hy, hz, rfl⟩,
    exact card_support_trinomial hkm hmn hx hy hz },
end

end polynomial


import tactic data.rat data.nat.prime data.real.irrational
import analysis.special_functions.pow

section sqrt2

open rat

/-
We prove that sqrt 2 is irrational. To make things simple, we work with natural numbers instead
of rational numbers. The idea is to derive a contradiction from the assumption that
`(a^2 = 2 * (b ^ 2))` for coprime natural nubmers `a` and `b`.
-/

-- First an auxiliary result to show `2 ∣ x` if `x * x = 2 * y`.
lemma aux {x y : ℕ} (h : x * x = 2 * y) : 2 ∣ x :=
(or_self (2 ∣ x)).mp $ (nat.prime.dvd_mul nat.prime_two).mp ⟨y, h⟩

-- Proof of the irrationality of sqrt 2.
example (a b : ℕ) (cop : nat.coprime a b) : ¬(a^2 = 2*b^2) :=
begin
  simp only [pow_two],
  intro h,
  have twodiva : 2 ∣ a := aux h,
  have twodiva' := twodiva, -- make a copy!
  cases twodiva' with m hm,
  rw [hm, mul_assoc] at h,
  have hdiv : m * (2* m) = b * b, from mul_left_cancel' two_ne_zero h,
  rw [mul_comm, mul_assoc] at hdiv,
  have twodivb : 2 ∣ b := aux hdiv.symm,
  exact nat.not_coprime_of_dvd_of_dvd one_lt_two twodiva twodivb cop,
end

/- example : ¬(∃ x : ℚ, x * x= 2) :=
λ h, exists.elim h $ λ x, num_denom_cases_on' x
(λ n d dne0 cop, _

) -/

/- example : ¬(∃ x : ℚ, x * x= 2) :=
λ h, exists.elim h $ λ x, (
  or.elim (em (x = 0))
  (λ hx0, by { rw [hx0, mul_zero], intro z2, exact two_ne_zero z2.symm, })
  (λ hxne0, num_denom_cases_on' x _)

) -/

end sqrt2

section irr_pow_irr

open real

/-
We show there exist irrational numbers `a` and `b` such that `a ^ b` is rational.
-/

-- A proof using `convert`.
example : ∃ a b : ℝ, irrational a ∧ irrational b ∧ ¬(irrational (a ^ b)) :=
begin
  have irrs2 := irrational_sqrt_two,
  by_cases h : irrational ((sqrt 2)^(sqrt 2)),
  { use [(sqrt 2)^(sqrt 2), sqrt 2],
    split, { exact h }, clear h,  split, { exact irrs2 },
    have ss_nat : sqrt 2 ^ 2 = 2 := sqr_sqrt zero_le_two,
    rw [←rpow_mul (sqrt_nonneg 2), ←pow_two, ss_nat],
    intro h₂,
    apply rat.not_irrational 2,
    convert h₂,
    have sqr_sqrt2 : (sqrt 2)^(2 : ℝ) = 2,
    { conv_rhs { rw ←ss_nat}, simp [←rpow_nat_cast], },
    simp [sqr_sqrt2], },
  { use [sqrt 2, sqrt 2], tauto, }
end

-- A proof without `convert`.
example : ∃ a b : ℝ, irrational a ∧ irrational b ∧ ¬(irrational (a ^ b)) :=
begin
  have irrs2 := irrational_sqrt_two,
  by_cases h : irrational ((sqrt 2)^(sqrt 2)),
  { use [(sqrt 2)^(sqrt 2), sqrt 2],
    split, { exact h }, clear h,  split, { exact irrs2 },
    have ss_nat : sqrt 2 ^ 2 = 2 := sqr_sqrt zero_le_two,
    rw [←rpow_mul (sqrt_nonneg 2), ←pow_two, ss_nat],
    rw (show (sqrt 2)^(2 : ℝ) = 2, by { conv_rhs { rw ←ss_nat}, simp [←rpow_nat_cast], } ),
    have := rat.not_irrational 2, simpa, },
  { use [sqrt 2, sqrt 2], tauto, }
end

-- A short proof.
example : ∃ a b : ℝ, irrational a ∧ irrational b ∧ ¬(irrational (a ^ b)) :=
begin
  have irrs2 := irrational_sqrt_two,
  by_cases h : irrational ((sqrt 2)^(sqrt 2)),
  { use [(sqrt 2)^(sqrt 2), sqrt 2],
    split, { exact h }, clear h,  split, { exact irrs2 },
    have ss_nat : sqrt 2 ^ 2 = 2 := sqr_sqrt zero_le_two,
    rw [←rpow_mul (sqrt_nonneg 2), ←pow_two, ss_nat],
    rw (show (sqrt 2)^(2 : ℝ) = 2, by { conv_rhs { rw ←ss_nat}, simp [←rpow_nat_cast], } ),
    convert rat.not_irrational 2, simp, },
  { use [sqrt 2, sqrt 2], tauto, }
end

end irr_pow_irr


section propositional

variables P Q R : Prop

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

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intros p np,
  contradiction,
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro np,
  by_contradiction hboom,
  contradiction,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,

  exact doubleneg_elim P,
  exact doubleneg_intro P,
end

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

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

    left,
    exact hq,
end

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

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro npq,
  intro p,
  cases npq with hnp hq,
  contradiction,
  exact hq
end

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

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro pq,
  intro nq,
  intro p,
  have hq : Q := pq p,
  contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro hnqp,
  intro p,
  by_contradiction,
  have np := hnqp h,
  contradiction,
end

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

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

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro n_p_or_np,
  have h : P ∨ ¬P,
  right,
  intro np,
  have h2 : P ∨ ¬P,
  left,
  exact np,
  contradiction,
  contradiction,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro p_q_p,
  intro np,
  by_cases h : (P → Q),

  have h2 : P := p_q_p h,
  contradiction,

  have h2 : (P → Q),
  intro p,
  contradiction,
  have h3 : P := p_q_p h2,
  contradiction,
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro p_or_q,
  intro np_or_nq,
  cases np_or_nq with hnp hnq,
  cases p_or_q with hp hq,
  contradiction,
  contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro p_and_q,
  intro np_or_nq,
  cases p_and_q with hp hq,
  cases np_or_nq with hnp hnq,
  contradiction,
  contradiction,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro n_p_or_q,
  split,

  intro p,
  have h2 : P ∨ Q,
  left,
  exact p,
  contradiction,

  intro q,
  have h2 : P ∨ Q,
  right,
  exact q,
  contradiction,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro np_and_nq,
  intro p_or_q,
  cases np_and_nq with hnp hnq,
  cases p_or_q with hp hq,
  contradiction,
  contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro p_and_q,
  by_cases q:Q,
  right,

  intro p,
  have hpq: P∧Q,
  split,
  exact p,
  exact q,
  contradiction,

  left,
  exact q,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro nq_or_np,
  intro p_and_q,
  cases p_and_q with p q,
  cases nq_or_np with hnq hnp,
  contradiction,
  contradiction,
end

-- try again withou magic
theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
  exact demorgan_conj P Q,
  exact demorgan_conj_converse P Q,
end

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

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

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro p_and_q_or_r,
  cases p_and_q_or_r with p q_or_r,
  cases q_or_r with q r,

  left,
  split,
  exact p,
  exact q,

  right,
  split,
  exact p,
  exact r,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro pq_pr,
  cases pq_pr with p_and_q p_and_r,
  cases p_and_q with p q,
  split,
  exact p,
  left,
  exact q,

  cases p_and_r with p r,
  split,
  exact p,
  right,
  exact r,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro p_or_qr,

  cases p_or_qr with p qr,
  split,
  left,
  exact p,
  left,
  exact p,

  cases qr with q r,
  split,
  right,
  exact q,
  right,
  exact r,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro pq_and_pr,
  cases pq_and_pr with pq pr,
  cases pq with p q,
  left,
  exact p,
  cases pr with p r,
  left,
  exact p,
  right,
  split,
  exact q,
  exact r,
end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro pq_r,
  intro p,
  intro q,
  have h : P ∧ Q,
  split,
  exact p,
  exact q,
  have r : R := pq_r h,
  exact r,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro p_q_r,
  intro pq,
  cases pq with p q,
  have q_r : Q → R := p_q_r p,
  have r : R := q_r q,
  exact r,
end

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

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

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

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

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

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro pq,
  cases pq with p q,
  exact p,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro pq,
  cases pq with p q,
  exact q,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
  intro pp,
  cases pp with p p,
  exact p,

  intro pp,
  split,
  exact pp,
  exact pp,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  intro pp,
  cases pp with p p,
  repeat {exact p},
  intro pp,
  left,
  exact pp,
end

end propositional

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

section predicate

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

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

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intro nex_px,
  intro x,
  intro n_px,
  have h : ∃x, P x,
  existsi x,
  exact n_px,
  contradiction,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro px_npx,
  intro ex_px,
  cases ex_px with x px,
  have h : ¬P x := px_npx x,
  contradiction,
end
theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro pu_px,
  by_contradiction ne,
  apply pu_px,
  intro x,
  by_contradiction npx,
  apply ne,
  existsi x,
  exact npx,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro e_npx,
  intro p_px,
  cases e_npx with x npx,
  have h : P x := p_px x,
  contradiction,
end

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

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

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro e_px,
  intro p_npx,
  cases e_px with x px,
  have h: ¬P x := p_npx x,
  contradiction,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro p_px,
  intro e_npx,
  cases e_npx with x npx,
  have h: P x := p_px x,
  contradiction,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intros n_e_npx x,
  by_contradiction npx,
  apply n_e_npx,
  existsi x,
  exact npx,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  rw contrapositive_law,
  rw doubleneg_law,
  exact demorgan_exists U P,
end

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

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

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

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro e_pxq,
  cases e_pxq with x pxq,
  cases pxq with px qx,

  split,
  existsi x,
  exact px,

  existsi x,
  exact qx,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro e_px_qx,
  cases e_px_qx with x q,
  cases q with px qx,
  left,
  existsi x,
  exact px,
  right,
  existsi x,
  exact qx,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro epx_eqx,
  cases epx_eqx with epx eqx,

  cases epx with x px,
  existsi x,
  left,
  exact px,

  cases eqx with x qx,
  existsi x,
  right,
  exact qx,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro p_px_qx,
  split,
  intro x,
  have h : P x ∧ Q x := p_px_qx x,
  cases h with px qx,
  exact px,

  intro x,
  have h: P x ∧ Q x := p_px_qx x,
  cases h with px qx,
  exact qx,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro p_px_qx,
  intro x,
  cases p_px_qx with px qx,

  split,
  have h : P x := px x,
  exact h,

  have h : Q x := qx x,
  exact h
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intro p_px_qx,
  intro x,
  cases p_px_qx with px qx,

  have h : P x := px x,
  left,
  exact h,

  have h : Q x := qx x,
  right,
  exact h,
end

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

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

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

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

end predicate


/-
Copyright (c) 2019 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov, Sébastien Gouëzel, Rémy Degenne
-/
import analysis.convex.specific_functions
import analysis.special_functions.pow
import data.real.conjugate_exponents
import tactic.nth_rewrite
import measure_theory.integration

/-!
# Mean value inequalities

In this file we prove several inequalities, including AM-GM inequality, Young's inequality,
Hölder inequality, and Minkowski inequality.

## Main theorems

### AM-GM inequality:

The inequality says that the geometric mean of a tuple of non-negative numbers is less than or equal
to their arithmetic mean. We prove the weighted version of this inequality: if $w$ and $z$
are two non-negative vectors and $\sum_{i\in s} w_i=1$, then
$$
\prod_{i\in s} z_i^{w_i} ≤ \sum_{i\in s} w_iz_i.
$$
The classical version is a special case of this inequality for $w_i=\frac{1}{n}$.

We prove a few versions of this inequality. Each of the following lemmas comes in two versions:
a version for real-valued non-negative functions is in the `real` namespace, and a version for
`nnreal`-valued functions is in the `nnreal` namespace.

- `geom_mean_le_arith_mean_weighted` : weighted version for functions on `finset`s;
- `geom_mean_le_arith_mean2_weighted` : weighted version for two numbers;
- `geom_mean_le_arith_mean3_weighted` : weighted version for three numbers;
- `geom_mean_le_arith_mean4_weighted` : weighted version for four numbers.

### Generalized mean inequality

The inequality says that for two non-negative vectors $w$ and $z$ with $\sum_{i\in s} w_i=1$
and $p ≤ q$ we have
$$
\sqrt[p]{\sum_{i\in s} w_i z_i^p} ≤ \sqrt[q]{\sum_{i\in s} w_i z_i^q}.
$$

Currently we only prove this inequality for $p=1$. As in the rest of `mathlib`, we provide
different theorems for natural exponents (`pow_arith_mean_le_arith_mean_pow`), integer exponents
(`fpow_arith_mean_le_arith_mean_fpow`), and real exponents (`rpow_arith_mean_le_arith_mean_rpow` and
`arith_mean_le_rpow_mean`). In the first two cases we prove
$$
\left(\sum_{i\in s} w_i z_i\right)^n ≤ \sum_{i\in s} w_i z_i^n
$$
in order to avoid using real exponents. For real exponents we prove both this and standard versions.

### Young's inequality

Young's inequality says that for non-negative numbers `a`, `b`, `p`, `q` such that
$\frac{1}{p}+\frac{1}{q}=1$ we have
$$
ab ≤ \frac{a^p}{p} + \frac{b^q}{q}.
$$

This inequality is a special case of the AM-GM inequality. It can be used to prove Hölder's
inequality (see below) but we use a different proof.

### Hölder's inequality

The inequality says that for two conjugate exponents `p` and `q` (i.e., for two positive numbers
such that $\frac{1}{p}+\frac{1}{q}=1$) and any two non-negative vectors their inner product is
less than or equal to the product of the $L_p$ norm of the first vector and the $L_q$ norm of the
second vector:
$$
\sum_{i\in s} a_ib_i ≤ \sqrt[p]{\sum_{i\in s} a_i^p}\sqrt[q]{\sum_{i\in s} b_i^q}.
$$

We give versions of this result in `ℝ`, `ℝ≥0` and `ℝ≥0∞`.

There are at least two short proofs of this inequality. In one proof we prenormalize both vectors,
then apply Young's inequality to each $a_ib_i$. We use a different proof deducing this inequality
from the generalized mean inequality for well-chosen vectors and weights.

Hölder's inequality for the Lebesgue integral of `ℝ≥0∞` and `ℝ≥0` functions: we prove
`∫ (f * g) ∂μ ≤ (∫ f^p ∂μ) ^ (1/p) * (∫ g^q ∂μ) ^ (1/q)` for `p`, `q` conjugate real exponents
and `α→(e)nnreal` functions in two cases,
* `ennreal.lintegral_mul_le_Lp_mul_Lq` : ℝ≥0∞ functions,
* `nnreal.lintegral_mul_le_Lp_mul_Lq`  : ℝ≥0 functions.

### Minkowski's inequality

The inequality says that for `p ≥ 1` the function
$$
\|a\|_p=\sqrt[p]{\sum_{i\in s} a_i^p}
$$
satisfies the triangle inequality $\|a+b\|_p\le \|a\|_p+\|b\|_p$.

We give versions of this result in `real`, `ℝ≥0` and `ℝ≥0∞`.

We deduce this inequality from Hölder's inequality. Namely, Hölder inequality implies that $\|a\|_p$
is the maximum of the inner product $\sum_{i\in s}a_ib_i$ over `b` such that $\|b\|_q\le 1$. Now
Minkowski's inequality follows from the fact that the maximum value of the sum of two functions is
less than or equal to the sum of the maximum values of the summands.

Minkowski's inequality for the Lebesgue integral of measurable functions with `ℝ≥0∞` values:
we prove `(∫ (f + g)^p ∂μ) ^ (1/p) ≤ (∫ f^p ∂μ) ^ (1/p) + (∫ g^p ∂μ) ^ (1/p)` for `1 ≤ p`.

## TODO

- each inequality `A ≤ B` should come with a theorem `A = B ↔ _`; one of the ways to prove them
  is to define `strict_convex_on` functions.
- generalized mean inequality with any `p ≤ q`, including negative numbers;
- prove that the power mean tends to the geometric mean as the exponent tends to zero.
- prove integral versions of these inequalities.

-/

universes u v

open finset
open_locale classical big_operators nnreal ennreal
noncomputable theory

variables {ι : Type u} (s : finset ι)

namespace real

/-- AM-GM inequality: the geometric mean is less than or equal to the arithmetic mean, weighted
version for real-valued nonnegative functions. -/
theorem geom_mean_le_arith_mean_weighted (w z : ι → ℝ) (hw : ∀ i ∈ s, 0 ≤ w i)
  (hw' : ∑ i in s, w i = 1) (hz : ∀ i ∈ s, 0 ≤ z i) :
  (∏ i in s, (z i) ^ (w i)) ≤ ∑ i in s, w i * z i :=
begin
  -- If some number `z i` equals zero and has non-zero weight, then LHS is 0 and RHS is nonnegative.
  by_cases A : ∃ i ∈ s, z i = 0 ∧ w i ≠ 0,
  { rcases A with ⟨i, his, hzi, hwi⟩,
    rw [prod_eq_zero his],
    { exact sum_nonneg (λ j hj, mul_nonneg (hw j hj) (hz j hj)) },
    { rw hzi, exact zero_rpow hwi } },
  -- If all numbers `z i` with non-zero weight are positive, then we apply Jensen's inequality
  -- for `exp` and numbers `log (z i)` with weights `w i`.
  { simp only [not_exists, not_and, ne.def, not_not] at A,
    have := convex_on_exp.map_sum_le hw hw' (λ i _, set.mem_univ $ log (z i)),
    simp only [exp_sum, (∘), smul_eq_mul, mul_comm (w _) (log _)] at this,
    convert this using 1; [apply prod_congr rfl, apply sum_congr rfl]; intros i hi,
    { cases eq_or_lt_of_le (hz i hi) with hz hz,
      { simp [A i hi hz.symm] },
      { exact rpow_def_of_pos hz _ } },
    { cases eq_or_lt_of_le (hz i hi) with hz hz,
      { simp [A i hi hz.symm] },
      { rw [exp_log hz] } } }
end

theorem pow_arith_mean_le_arith_mean_pow (w z : ι → ℝ) (hw : ∀ i ∈ s, 0 ≤ w i)
  (hw' : ∑ i in s, w i = 1) (hz : ∀ i ∈ s, 0 ≤ z i) (n : ℕ) :
  (∑ i in s, w i * z i) ^ n ≤ ∑ i in s, (w i * z i ^ n) :=
(convex_on_pow n).map_sum_le hw hw' hz

theorem pow_arith_mean_le_arith_mean_pow_of_even (w z : ι → ℝ) (hw : ∀ i ∈ s, 0 ≤ w i)
  (hw' : ∑ i in s, w i = 1) {n : ℕ} (hn : even n) :
  (∑ i in s, w i * z i) ^ n ≤ ∑ i in s, (w i * z i ^ n) :=
(convex_on_pow_of_even hn).map_sum_le hw hw' (λ _ _, trivial)

theorem fpow_arith_mean_le_arith_mean_fpow (w z : ι → ℝ) (hw : ∀ i ∈ s, 0 ≤ w i)
  (hw' : ∑ i in s, w i = 1) (hz : ∀ i ∈ s, 0 < z i) (m : ℤ) :
  (∑ i in s, w i * z i) ^ m ≤ ∑ i in s, (w i * z i ^ m) :=
(convex_on_fpow m).map_sum_le hw hw' hz

theorem rpow_arith_mean_le_arith_mean_rpow (w z : ι → ℝ) (hw : ∀ i ∈ s, 0 ≤ w i)
  (hw' : ∑ i in s, w i = 1) (hz : ∀ i ∈ s, 0 ≤ z i) {p : ℝ} (hp : 1 ≤ p) :
  (∑ i in s, w i * z i) ^ p ≤ ∑ i in s, (w i * z i ^ p) :=
(convex_on_rpow hp).map_sum_le hw hw' hz

theorem arith_mean_le_rpow_mean (w z : ι → ℝ) (hw : ∀ i ∈ s, 0 ≤ w i)
  (hw' : ∑ i in s, w i = 1) (hz : ∀ i ∈ s, 0 ≤ z i) {p : ℝ} (hp : 1 ≤ p) :
  ∑ i in s, w i * z i ≤ (∑ i in s, (w i * z i ^ p)) ^ (1 / p) :=
begin
  have : 0 < p := lt_of_lt_of_le zero_lt_one hp,
  rw [← rpow_le_rpow_iff _ _ this, ← rpow_mul, one_div_mul_cancel (ne_of_gt this), rpow_one],
  exact rpow_arith_mean_le_arith_mean_rpow s w z hw hw' hz hp,
  all_goals { apply_rules [sum_nonneg, rpow_nonneg_of_nonneg],
    intros i hi,
    apply_rules [mul_nonneg, rpow_nonneg_of_nonneg, hw i hi, hz i hi] },
end

end real

namespace nnreal

/-- The geometric mean is less than or equal to the arithmetic mean, weighted version
for `nnreal`-valued functions. -/
theorem geom_mean_le_arith_mean_weighted (w z : ι → ℝ≥0) (hw' : ∑ i in s, w i = 1) :
  (∏ i in s, (z i) ^ (w i:ℝ)) ≤ ∑ i in s, w i * z i :=
by exact_mod_cast real.geom_mean_le_arith_mean_weighted _ _ _ (λ i _, (w i).coe_nonneg)
  (by assumption_mod_cast) (λ i _, (z i).coe_nonneg)

/-- The geometric mean is less than or equal to the arithmetic mean, weighted version
for two `nnreal` numbers. -/
theorem geom_mean_le_arith_mean2_weighted (w₁ w₂ p₁ p₂ : ℝ≥0) :
  w₁ + w₂ = 1 → p₁ ^ (w₁:ℝ) * p₂ ^ (w₂:ℝ) ≤ w₁ * p₁ + w₂ * p₂ :=
by simpa only [fin.prod_univ_succ, fin.sum_univ_succ, fin.prod_univ_zero, fin.sum_univ_zero,
  fin.cons_succ, fin.cons_zero, add_zero, mul_one]
using geom_mean_le_arith_mean_weighted (univ : finset (fin 2))
  (fin.cons w₁ $ fin.cons w₂ fin_zero_elim) (fin.cons p₁ $ fin.cons p₂ $ fin_zero_elim)

theorem geom_mean_le_arith_mean3_weighted (w₁ w₂ w₃ p₁ p₂ p₃ : ℝ≥0) :
  w₁ + w₂ + w₃ = 1 → p₁ ^ (w₁:ℝ) * p₂ ^ (w₂:ℝ) * p₃ ^ (w₃:ℝ) ≤ w₁ * p₁ + w₂ * p₂ + w₃ * p₃ :=
by simpa only  [fin.prod_univ_succ, fin.sum_univ_succ, fin.prod_univ_zero, fin.sum_univ_zero,
  fin.cons_succ, fin.cons_zero, add_zero, mul_one, ← add_assoc, mul_assoc]
using geom_mean_le_arith_mean_weighted (univ : finset (fin 3))
  (fin.cons w₁ $ fin.cons w₂ $ fin.cons w₃ fin_zero_elim)
  (fin.cons p₁ $ fin.cons p₂ $ fin.cons p₃ fin_zero_elim)

theorem geom_mean_le_arith_mean4_weighted (w₁ w₂ w₃ w₄ p₁ p₂ p₃ p₄ : ℝ≥0) :
  w₁ + w₂ + w₃ + w₄ = 1 → p₁ ^ (w₁:ℝ) * p₂ ^ (w₂:ℝ) * p₃ ^ (w₃:ℝ)* p₄ ^ (w₄:ℝ) ≤
    w₁ * p₁ + w₂ * p₂ + w₃ * p₃ + w₄ * p₄ :=
by simpa only  [fin.prod_univ_succ, fin.sum_univ_succ, fin.prod_univ_zero, fin.sum_univ_zero,
  fin.cons_succ, fin.cons_zero, add_zero, mul_one, ← add_assoc, mul_assoc]
using geom_mean_le_arith_mean_weighted (univ : finset (fin 4))
  (fin.cons w₁ $ fin.cons w₂ $ fin.cons w₃ $ fin.cons w₄ fin_zero_elim)
  (fin.cons p₁ $ fin.cons p₂ $ fin.cons p₃ $ fin.cons p₄ fin_zero_elim)

/-- Weighted generalized mean inequality, version sums over finite sets, with `ℝ≥0`-valued
functions and natural exponent. -/
theorem pow_arith_mean_le_arith_mean_pow (w z : ι → ℝ≥0) (hw' : ∑ i in s, w i = 1) (n : ℕ) :
  (∑ i in s, w i * z i) ^ n ≤ ∑ i in s, (w i * z i ^ n) :=
by exact_mod_cast real.pow_arith_mean_le_arith_mean_pow s _ _ (λ i _, (w i).coe_nonneg)
  (by exact_mod_cast hw') (λ i _, (z i).coe_nonneg) n

/-- Weighted generalized mean inequality, version for sums over finite sets, with `ℝ≥0`-valued
functions and real exponents. -/
theorem rpow_arith_mean_le_arith_mean_rpow (w z : ι → ℝ≥0) (hw' : ∑ i in s, w i = 1) {p : ℝ}
  (hp : 1 ≤ p) :
  (∑ i in s, w i * z i) ^ p ≤ ∑ i in s, (w i * z i ^ p) :=
by exact_mod_cast real.rpow_arith_mean_le_arith_mean_rpow s _ _ (λ i _, (w i).coe_nonneg)
  (by exact_mod_cast hw') (λ i _, (z i).coe_nonneg) hp

/-- Weighted generalized mean inequality, version for two elements of `ℝ≥0` and real exponents. -/
theorem rpow_arith_mean_le_arith_mean2_rpow (w₁ w₂ z₁ z₂ : ℝ≥0) (hw' : w₁ + w₂ = 1) {p : ℝ}
  (hp : 1 ≤ p) :
  (w₁ * z₁ + w₂ * z₂) ^ p ≤ w₁ * z₁ ^ p + w₂ * z₂ ^ p :=
begin
  have h := rpow_arith_mean_le_arith_mean_rpow (univ : finset (fin 2))
    (fin.cons w₁ $ fin.cons w₂ fin_zero_elim) (fin.cons z₁ $ fin.cons z₂ $ fin_zero_elim) _ hp,
  { simpa [fin.sum_univ_succ, fin.sum_univ_zero, fin.cons_succ, fin.cons_zero] using h, },
  { simp [hw', fin.sum_univ_succ, fin.sum_univ_zero, fin.cons_succ, fin.cons_zero], },
end

/-- Weighted generalized mean inequality, version for sums over finite sets, with `ℝ≥0`-valued
functions and real exponents. -/
theorem arith_mean_le_rpow_mean (w z : ι → ℝ≥0) (hw' : ∑ i in s, w i = 1) {p : ℝ}
  (hp : 1 ≤ p) :
  ∑ i in s, w i * z i ≤ (∑ i in s, (w i * z i ^ p)) ^ (1 / p) :=
by exact_mod_cast real.arith_mean_le_rpow_mean s _ _ (λ i _, (w i).coe_nonneg)
  (by exact_mod_cast hw') (λ i _, (z i).coe_nonneg) hp

end nnreal

namespace ennreal

/-- Weighted generalized mean inequality, version for sums over finite sets, with `ℝ≥0∞`-valued
functions and real exponents. -/
theorem rpow_arith_mean_le_arith_mean_rpow (w z : ι → ℝ≥0∞) (hw' : ∑ i in s, w i = 1) {p : ℝ}
  (hp : 1 ≤ p) :
  (∑ i in s, w i * z i) ^ p ≤ ∑ i in s, (w i * z i ^ p) :=
begin
  have hp_pos : 0 < p, from lt_of_lt_of_le zero_lt_one hp,
  have hp_nonneg : 0 ≤ p, from le_of_lt hp_pos,
  have hp_not_nonpos : ¬ p ≤ 0, by simp [hp_pos],
  have hp_not_neg : ¬ p < 0, by simp [hp_nonneg],
  have h_top_iff_rpow_top : ∀ (i : ι) (hi : i ∈ s), w i * z i = ⊤ ↔ w i * (z i) ^ p = ⊤,
  by simp [hp_pos, hp_nonneg, hp_not_nonpos, hp_not_neg],
  refine le_of_top_imp_top_of_to_nnreal_le _ _,
  { -- first, prove `(∑ i in s, w i * z i) ^ p = ⊤ → ∑ i in s, (w i * z i ^ p) = ⊤`
    rw [rpow_eq_top_iff, sum_eq_top_iff, sum_eq_top_iff],
    intro h,
    simp only [and_false, hp_not_neg, false_or] at h,
    rcases h.left with ⟨a, H, ha⟩,
    use [a, H],
    rwa ←h_top_iff_rpow_top a H, },
  { -- second, suppose both `(∑ i in s, w i * z i) ^ p ≠ ⊤` and `∑ i in s, (w i * z i ^ p) ≠ ⊤`,
    -- and prove `((∑ i in s, w i * z i) ^ p).to_nnreal ≤ (∑ i in s, (w i * z i ^ p)).to_nnreal`,
    -- by using `nnreal.rpow_arith_mean_le_arith_mean_rpow`.
    intros h_top_rpow_sum _,
    -- show hypotheses needed to put the `.to_nnreal` inside the sums.
    have h_top : ∀ (a : ι), a ∈ s → w a * z a < ⊤,
    { have h_top_sum : ∑ (i : ι) in s, w i * z i < ⊤,
      { by_contra h,
        rw [lt_top_iff_ne_top, not_not] at h,
        rw [h, top_rpow_of_pos hp_pos] at h_top_rpow_sum,
        exact h_top_rpow_sum rfl, },
      rwa sum_lt_top_iff at h_top_sum, },
    have h_top_rpow : ∀ (a : ι), a ∈ s → w a * z a ^ p < ⊤,
    { intros i hi,
      specialize h_top i hi,
      rw lt_top_iff_ne_top at h_top ⊢,
      rwa [ne.def, ←h_top_iff_rpow_top i hi], },
    -- put the `.to_nnreal` inside the sums.
    simp_rw [to_nnreal_sum h_top_rpow, ←to_nnreal_rpow, to_nnreal_sum h_top, to_nnreal_mul,
      ←to_nnreal_rpow],
    -- use corresponding nnreal result
    refine nnreal.rpow_arith_mean_le_arith_mean_rpow s (λ i, (w i).to_nnreal) (λ i, (z i).to_nnreal)
      _ hp,
    -- verify the hypothesis `∑ i in s, (w i).to_nnreal = 1`, using `∑ i in s, w i = 1` .
    have h_sum_nnreal : (∑ i in s, w i) = ↑(∑ i in s, (w i).to_nnreal),
    { have hw_top : ∑ i in s, w i < ⊤, by { rw hw', exact one_lt_top, },
      rw ←to_nnreal_sum,
      { rw coe_to_nnreal,
        rwa ←lt_top_iff_ne_top, },
      { rwa sum_lt_top_iff at hw_top, }, },
    rwa [←coe_eq_coe, ←h_sum_nnreal], },
end

/-- Weighted generalized mean inequality, version for two elements of `ℝ≥0∞` and real
exponents. -/
theorem rpow_arith_mean_le_arith_mean2_rpow (w₁ w₂ z₁ z₂ : ℝ≥0∞) (hw' : w₁ + w₂ = 1) {p : ℝ}
  (hp : 1 ≤ p) :
  (w₁ * z₁ + w₂ * z₂) ^ p ≤ w₁ * z₁ ^ p + w₂ * z₂ ^ p :=
begin
  have h := rpow_arith_mean_le_arith_mean_rpow (univ : finset (fin 2))
    (fin.cons w₁ $ fin.cons w₂ fin_zero_elim) (fin.cons z₁ $ fin.cons z₂ $ fin_zero_elim) _ hp,
  { simpa [fin.sum_univ_succ, fin.sum_univ_zero, fin.cons_succ, fin.cons_zero] using h, },
  { simp [hw', fin.sum_univ_succ, fin.sum_univ_zero, fin.cons_succ, fin.cons_zero], },
end

end ennreal

namespace real

theorem geom_mean_le_arith_mean2_weighted {w₁ w₂ p₁ p₂ : ℝ} (hw₁ : 0 ≤ w₁) (hw₂ : 0 ≤ w₂)
  (hp₁ : 0 ≤ p₁) (hp₂ : 0 ≤ p₂) (hw : w₁ + w₂ = 1) :
  p₁ ^ w₁ * p₂ ^ w₂ ≤ w₁ * p₁ + w₂ * p₂ :=
nnreal.geom_mean_le_arith_mean2_weighted ⟨w₁, hw₁⟩ ⟨w₂, hw₂⟩ ⟨p₁, hp₁⟩ ⟨p₂, hp₂⟩ $
  nnreal.coe_eq.1 $ by assumption

theorem geom_mean_le_arith_mean3_weighted {w₁ w₂ w₃ p₁ p₂ p₃ : ℝ} (hw₁ : 0 ≤ w₁) (hw₂ : 0 ≤ w₂)
  (hw₃ : 0 ≤ w₃) (hp₁ : 0 ≤ p₁) (hp₂ : 0 ≤ p₂) (hp₃ : 0 ≤ p₃) (hw : w₁ + w₂ + w₃ = 1) :
  p₁ ^ w₁ * p₂ ^ w₂ * p₃ ^ w₃ ≤ w₁ * p₁ + w₂ * p₂ + w₃ * p₃ :=
nnreal.geom_mean_le_arith_mean3_weighted
  ⟨w₁, hw₁⟩ ⟨w₂, hw₂⟩ ⟨w₃, hw₃⟩ ⟨p₁, hp₁⟩ ⟨p₂, hp₂⟩ ⟨p₃, hp₃⟩ $ nnreal.coe_eq.1 hw

theorem geom_mean_le_arith_mean4_weighted {w₁ w₂ w₃ w₄ p₁ p₂ p₃ p₄ : ℝ} (hw₁ : 0 ≤ w₁)
  (hw₂ : 0 ≤ w₂) (hw₃ : 0 ≤ w₃) (hw₄ : 0 ≤ w₄) (hp₁ : 0 ≤ p₁) (hp₂ : 0 ≤ p₂) (hp₃ : 0 ≤ p₃)
  (hp₄ : 0 ≤ p₄) (hw : w₁ + w₂ + w₃ + w₄ = 1) :
  p₁ ^ w₁ * p₂ ^ w₂ * p₃ ^ w₃ * p₄ ^ w₄ ≤ w₁ * p₁ + w₂ * p₂ + w₃ * p₃ + w₄ * p₄ :=
nnreal.geom_mean_le_arith_mean4_weighted ⟨w₁, hw₁⟩ ⟨w₂, hw₂⟩ ⟨w₃, hw₃⟩ ⟨w₄, hw₄⟩
  ⟨p₁, hp₁⟩ ⟨p₂, hp₂⟩ ⟨p₃, hp₃⟩ ⟨p₄, hp₄⟩ $ nnreal.coe_eq.1 $ by assumption

/-- Young's inequality, a version for nonnegative real numbers. -/
theorem young_inequality_of_nonneg {a b p q : ℝ} (ha : 0 ≤ a) (hb : 0 ≤ b)
  (hpq : p.is_conjugate_exponent q) :
  a * b ≤ a^p / p + b^q / q :=
by simpa [← rpow_mul, ha, hb, hpq.ne_zero, hpq.symm.ne_zero, div_eq_inv_mul]
  using geom_mean_le_arith_mean2_weighted hpq.one_div_nonneg hpq.symm.one_div_nonneg
    (rpow_nonneg_of_nonneg ha p) (rpow_nonneg_of_nonneg hb q) hpq.inv_add_inv_conj

/-- Young's inequality, a version for arbitrary real numbers. -/
theorem young_inequality (a b : ℝ) {p q : ℝ} (hpq : p.is_conjugate_exponent q) :
  a * b ≤ (abs a)^p / p + (abs b)^q / q :=
calc a * b ≤ abs (a * b)                   : le_abs_self (a * b)
       ... = abs a * abs b                 : abs_mul a b
       ... ≤ (abs a)^p / p + (abs b)^q / q :
  real.young_inequality_of_nonneg (abs_nonneg a) (abs_nonneg b) hpq

end real

namespace nnreal

/-- Young's inequality, `ℝ≥0` version. We use `{p q : ℝ≥0}` in order to avoid constructing
witnesses of `0 ≤ p` and `0 ≤ q` for the denominators.  -/
theorem young_inequality (a b : ℝ≥0) {p q : ℝ≥0} (hp : 1 < p) (hpq : 1 / p + 1 / q = 1) :
  a * b ≤ a^(p:ℝ) / p + b^(q:ℝ) / q :=
real.young_inequality_of_nonneg a.coe_nonneg b.coe_nonneg ⟨hp, nnreal.coe_eq.2 hpq⟩

/-- Young's inequality, `ℝ≥0` version with real conjugate exponents. -/
theorem young_inequality_real (a b : ℝ≥0) {p q : ℝ} (hpq : p.is_conjugate_exponent q) :
  a * b ≤ a ^ p / nnreal.of_real p + b ^ q / nnreal.of_real q :=
begin
  nth_rewrite 0 ←coe_of_real p hpq.nonneg,
  nth_rewrite 0 ←coe_of_real q hpq.symm.nonneg,
  exact young_inequality a b hpq.one_lt_nnreal hpq.inv_add_inv_conj_nnreal,
end

/-- Hölder inequality: the scalar product of two functions is bounded by the product of their
`L^p` and `L^q` norms when `p` and `q` are conjugate exponents. Version for sums over finite sets,
with `ℝ≥0`-valued functions. -/
theorem inner_le_Lp_mul_Lq (f g : ι → ℝ≥0) {p q : ℝ}
  (hpq : p.is_conjugate_exponent q) :
  ∑ i in s, f i * g i ≤ (∑ i in s, (f i) ^ p) ^ (1 / p) * (∑ i in s, (g i) ^ q) ^ (1 / q) :=
begin
  -- Let `G=∥g∥_q` be the `L_q`-norm of `g`.
  set G := (∑ i in s, (g i) ^ q) ^ (1 / q),
  have hGq : G ^ q = ∑ i in s, (g i) ^ q,
  { rw [← rpow_mul, one_div_mul_cancel hpq.symm.ne_zero, rpow_one], },
  -- First consider the trivial case `∥g∥_q=0`
  by_cases hG : G = 0,
  { rw [hG, sum_eq_zero, mul_zero],
    intros i hi,
    simp only [rpow_eq_zero_iff, sum_eq_zero_iff] at hG,
    simp [(hG.1 i hi).1] },
  { -- Move power from right to left
    rw [← div_le_iff hG, sum_div],
    -- Now the inequality follows from the weighted generalized mean inequality
    -- with weights `w_i` and numbers `z_i` given by the following formulas.
    set w : ι → ℝ≥0 := λ i, (g i) ^ q / G ^ q,
    set z : ι → ℝ≥0 := λ i, f i * (G / g i) ^ (q / p),
    -- Show that the sum of weights equals one
    have A : ∑ i in s, w i = 1,
    { rw [← sum_div, hGq, div_self],
      simpa [rpow_eq_zero_iff, hpq.symm.ne_zero] using hG },
    -- LHS of the goal equals LHS of the weighted generalized mean inequality
    calc (∑ i in s, f i * g i / G) = (∑ i in s, w i * z i) :
      begin
        refine sum_congr rfl (λ i hi, _),
        have : q - q / p = 1, by field_simp [hpq.ne_zero, hpq.symm.mul_eq_add],
        dsimp only [w, z],
        rw [← div_rpow, mul_left_comm, mul_div_assoc, ← @inv_div _ _ _ G, inv_rpow,
          ← div_eq_mul_inv, ← rpow_sub']; simp [this]
      end
    -- Apply the generalized mean inequality
    ... ≤ (∑ i in s, w i * (z i) ^ p) ^ (1 / p) :
      nnreal.arith_mean_le_rpow_mean s w z A (le_of_lt hpq.one_lt)
    -- Simplify the right hand side. Terms with `g i ≠ 0` are equal to `(f i) ^ p`,
    -- the others are zeros.
    ... ≤ (∑ i in s, (f i) ^ p) ^ (1 / p) :
      begin
        refine rpow_le_rpow (sum_le_sum (λ i hi, _)) hpq.one_div_nonneg,
        dsimp only [w, z],
        rw [mul_rpow, mul_left_comm, ← rpow_mul _ _ p, div_mul_cancel _ hpq.ne_zero, div_rpow,
          div_mul_div, mul_comm (G ^ q), mul_div_mul_right],
        { nth_rewrite 1 [← mul_one ((f i) ^ p)],
          exact canonically_ordered_semiring.mul_le_mul (le_refl _) (div_self_le _) },
        { simpa [hpq.symm.ne_zero] using hG }
      end }
end

/-- The `L_p` seminorm of a vector `f` is the greatest value of the inner product
`∑ i in s, f i * g i` over functions `g` of `L_q` seminorm less than or equal to one. -/
theorem is_greatest_Lp (f : ι → ℝ≥0) {p q : ℝ} (hpq : p.is_conjugate_exponent q) :
  is_greatest ((λ g : ι → ℝ≥0, ∑ i in s, f i * g i) ''
    {g | ∑ i in s, (g i)^q ≤ 1}) ((∑ i in s, (f i)^p) ^ (1 / p)) :=
begin
  split,
  { use λ i, ((f i) ^ p / f i / (∑ i in s, (f i) ^ p) ^ (1 / q)),
    by_cases hf : ∑ i in s, (f i)^p = 0,
    { simp [hf, hpq.ne_zero, hpq.symm.ne_zero] },
    { have A : p + q - q ≠ 0, by simp [hpq.ne_zero],
      have B : ∀ y : ℝ≥0, y * y^p / y = y^p,
      { refine λ y, mul_div_cancel_left_of_imp (λ h, _),
        simpa [h, hpq.ne_zero] },
      simp only [set.mem_set_of_eq, div_rpow, ← sum_div, ← rpow_mul,
        div_mul_cancel _ hpq.symm.ne_zero, rpow_one, div_le_iff hf, one_mul, hpq.mul_eq_add,
        ← rpow_sub' _ A, _root_.add_sub_cancel, le_refl, true_and, ← mul_div_assoc, B],
      rw [div_eq_iff, ← rpow_add hf, hpq.inv_add_inv_conj, rpow_one],
      simpa [hpq.symm.ne_zero] using hf } },
  { rintros _ ⟨g, hg, rfl⟩,
    apply le_trans (inner_le_Lp_mul_Lq s f g hpq),
    simpa only [mul_one] using canonically_ordered_semiring.mul_le_mul (le_refl _)
      (nnreal.rpow_le_one hg (le_of_lt hpq.symm.one_div_pos)) }
end

/-- Minkowski inequality: the `L_p` seminorm of the sum of two vectors is less than or equal
to the sum of the `L_p`-seminorms of the summands. A version for `nnreal`-valued functions. -/
theorem Lp_add_le (f g : ι → ℝ≥0) {p : ℝ} (hp : 1 ≤ p) :
  (∑ i in s, (f i + g i) ^ p) ^ (1 / p) ≤
    (∑ i in s, (f i) ^ p) ^ (1 / p) + (∑ i in s, (g i) ^ p) ^ (1 / p) :=
begin
  -- The result is trivial when `p = 1`, so we can assume `1 < p`.
  rcases eq_or_lt_of_le hp with rfl|hp, { simp [finset.sum_add_distrib] },
  have hpq := real.is_conjugate_exponent_conjugate_exponent hp,
  have := is_greatest_Lp s (f + g) hpq,
  simp only [pi.add_apply, add_mul, sum_add_distrib] at this,
  rcases this.1 with ⟨φ, hφ, H⟩,
  rw ← H,
  exact add_le_add ((is_greatest_Lp s f hpq).2 ⟨φ, hφ, rfl⟩)
    ((is_greatest_Lp s g hpq).2 ⟨φ, hφ, rfl⟩)
end

end nnreal

namespace real

variables (f g : ι → ℝ)  {p q : ℝ}

/-- Hölder inequality: the scalar product of two functions is bounded by the product of their
`L^p` and `L^q` norms when `p` and `q` are conjugate exponents. Version for sums over finite sets,
with real-valued functions. -/
theorem inner_le_Lp_mul_Lq (hpq : is_conjugate_exponent p q) :
  ∑ i in s, f i * g i ≤ (∑ i in s, (abs $ f i)^p) ^ (1 / p) * (∑ i in s, (abs $ g i)^q) ^ (1 / q) :=
begin
  have := nnreal.coe_le_coe.2 (nnreal.inner_le_Lp_mul_Lq s (λ i, ⟨_, abs_nonneg (f i)⟩)
    (λ i, ⟨_, abs_nonneg (g i)⟩) hpq),
  push_cast at this,
  refine le_trans (sum_le_sum $ λ i hi, _) this,
  simp only [← abs_mul, le_abs_self]
end

/-- Minkowski inequality: the `L_p` seminorm of the sum of two vectors is less than or equal
to the sum of the `L_p`-seminorms of the summands. A version for `real`-valued functions. -/
theorem Lp_add_le (hp : 1 ≤ p) :
  (∑ i in s, (abs $ f i + g i) ^ p) ^ (1 / p) ≤
    (∑ i in s, (abs $ f i) ^ p) ^ (1 / p) + (∑ i in s, (abs $ g i) ^ p) ^ (1 / p) :=
begin
  have := nnreal.coe_le_coe.2 (nnreal.Lp_add_le s (λ i, ⟨_, abs_nonneg (f i)⟩)
    (λ i, ⟨_, abs_nonneg (g i)⟩) hp),
  push_cast at this,
  refine le_trans (rpow_le_rpow _ (sum_le_sum $ λ i hi, _) _) this;
    simp [sum_nonneg, rpow_nonneg_of_nonneg, abs_nonneg, le_trans zero_le_one hp, abs_add,
      rpow_le_rpow]
end

variables {f g}

/-- Hölder inequality: the scalar product of two functions is bounded by the product of their
`L^p` and `L^q` norms when `p` and `q` are conjugate exponents. Version for sums over finite sets,
with real-valued nonnegative functions. -/
theorem inner_le_Lp_mul_Lq_of_nonneg (hpq : is_conjugate_exponent p q)
  (hf : ∀ i ∈ s, 0 ≤ f i) (hg : ∀ i ∈ s, 0 ≤ g i) :
  ∑ i in s, f i * g i ≤ (∑ i in s, (f i)^p) ^ (1 / p) * (∑ i in s, (g i)^q) ^ (1 / q) :=
by convert inner_le_Lp_mul_Lq s f g hpq using 3; apply sum_congr rfl; intros i hi;
  simp only [abs_of_nonneg, hf i hi, hg i hi]

/-- Minkowski inequality: the `L_p` seminorm of the sum of two vectors is less than or equal
to the sum of the `L_p`-seminorms of the summands. A version for `real`-valued nonnegative
functions. -/
theorem Lp_add_le_of_nonneg (hp : 1 ≤ p) (hf : ∀ i ∈ s, 0 ≤ f i) (hg : ∀ i ∈ s, 0 ≤ g i) :
  (∑ i in s, (f i + g i) ^ p) ^ (1 / p) ≤
    (∑ i in s, (f i) ^ p) ^ (1 / p) + (∑ i in s, (g i) ^ p) ^ (1 / p) :=
by convert Lp_add_le s f g hp using 2 ; [skip, congr' 1, congr' 1];
  apply sum_congr rfl; intros i hi; simp only [abs_of_nonneg, hf i hi, hg i hi, add_nonneg]

end real

namespace ennreal

/-- Young's inequality, `ℝ≥0∞` version with real conjugate exponents. -/
theorem young_inequality (a b : ℝ≥0∞) {p q : ℝ} (hpq : p.is_conjugate_exponent q) :
  a * b ≤ a ^ p / ennreal.of_real p + b ^ q / ennreal.of_real q :=
begin
  by_cases h : a = ⊤ ∨ b = ⊤,
  { refine le_trans le_top (le_of_eq _),
    repeat { rw div_eq_mul_inv },
    cases h; rw h; simp [h, hpq.pos, hpq.symm.pos], },
  push_neg at h, -- if a ≠ ⊤ and b ≠ ⊤, use the nnreal version: nnreal.young_inequality_real
  rw [←coe_to_nnreal h.left, ←coe_to_nnreal h.right, ←coe_mul,
    coe_rpow_of_nonneg _ hpq.nonneg, coe_rpow_of_nonneg _ hpq.symm.nonneg, ennreal.of_real,
    ennreal.of_real, ←@coe_div (nnreal.of_real p) _ (by simp [hpq.pos]),
    ←@coe_div (nnreal.of_real q) _ (by simp [hpq.symm.pos]), ←coe_add, coe_le_coe],
  exact nnreal.young_inequality_real a.to_nnreal b.to_nnreal hpq,
end

variables (f g : ι → ℝ≥0∞)  {p q : ℝ}

/-- Hölder inequality: the scalar product of two functions is bounded by the product of their
`L^p` and `L^q` norms when `p` and `q` are conjugate exponents. Version for sums over finite sets,
with `ℝ≥0∞`-valued functions. -/
theorem inner_le_Lp_mul_Lq (hpq : p.is_conjugate_exponent q) :
  (∑ i in s, f i * g i) ≤ (∑ i in s, (f i)^p) ^ (1/p) * (∑ i in s, (g i)^q) ^ (1/q) :=
begin
  by_cases H : (∑ i in s, (f i)^p) ^ (1/p) = 0 ∨ (∑ i in s, (g i)^q) ^ (1/q) = 0,
  { replace H : (∀ i ∈ s, f i = 0) ∨ (∀ i ∈ s, g i = 0),
      by simpa [ennreal.rpow_eq_zero_iff, hpq.pos, hpq.symm.pos, asymm hpq.pos, asymm hpq.symm.pos,
                sum_eq_zero_iff_of_nonneg] using H,
    have : ∀ i ∈ s, f i * g i = 0 := λ i hi, by cases H; simp [H i hi],
    have : (∑ i in s, f i * g i) = (∑ i in s, 0) := sum_congr rfl this,
    simp [this] },
  push_neg at H,
  by_cases H' : (∑ i in s, (f i)^p) ^ (1/p) = ⊤ ∨ (∑ i in s, (g i)^q) ^ (1/q) = ⊤,
  { cases H'; simp [H', -one_div, H] },
  replace H' : (∀ i ∈ s, f i ≠ ⊤) ∧ (∀ i ∈ s, g i ≠ ⊤),
    by simpa [ennreal.rpow_eq_top_iff, asymm hpq.pos, asymm hpq.symm.pos, hpq.pos, hpq.symm.pos,
              ennreal.sum_eq_top_iff, not_or_distrib] using H',
  have := ennreal.coe_le_coe.2 (@nnreal.inner_le_Lp_mul_Lq _ s (λ i, ennreal.to_nnreal (f i))
              (λ i, ennreal.to_nnreal (g i)) _ _ hpq),
  simp [← ennreal.coe_rpow_of_nonneg, le_of_lt (hpq.pos), le_of_lt (hpq.one_div_pos),
             le_of_lt (hpq.symm.pos), le_of_lt (hpq.symm.one_div_pos)] at this,
  convert this using 1;
  [skip, congr' 2];
  [skip, skip, simp, skip, simp];
  { apply finset.sum_congr rfl (λ i hi, _), simp [H'.1 i hi, H'.2 i hi, -with_zero.coe_mul,
    with_top.coe_mul.symm] },
end

/-- Minkowski inequality: the `L_p` seminorm of the sum of two vectors is less than or equal
to the sum of the `L_p`-seminorms of the summands. A version for `ℝ≥0∞` valued nonnegative
functions. -/
theorem Lp_add_le (hp : 1 ≤ p) :
  (∑ i in s, (f i + g i) ^ p)^(1/p) ≤ (∑ i in s, (f i)^p) ^ (1/p) + (∑ i in s, (g i)^p) ^ (1/p) :=
begin
  by_cases H' : (∑ i in s, (f i)^p) ^ (1/p) = ⊤ ∨ (∑ i in s, (g i)^p) ^ (1/p) = ⊤,
  { cases H'; simp [H', -one_div] },
  have pos : 0 < p := lt_of_lt_of_le zero_lt_one hp,
  replace H' : (∀ i ∈ s, f i ≠ ⊤) ∧ (∀ i ∈ s, g i ≠ ⊤),
    by simpa [ennreal.rpow_eq_top_iff, asymm pos, pos, ennreal.sum_eq_top_iff,
              not_or_distrib] using H',
  have := ennreal.coe_le_coe.2 (@nnreal.Lp_add_le _ s (λ i, ennreal.to_nnreal (f i))
              (λ i, ennreal.to_nnreal (g i)) _  hp),
  push_cast [← ennreal.coe_rpow_of_nonneg, le_of_lt (pos), le_of_lt (one_div_pos.2 pos)] at this,
  convert this using 2;
  [skip, congr' 1, congr' 1];
  { apply finset.sum_congr rfl (λ i hi, _), simp [H'.1 i hi, H'.2 i hi] }
end

private lemma add_rpow_le_one_of_add_le_one {p : ℝ} (a b : ℝ≥0∞) (hab : a + b ≤ 1)
  (hp1 : 1 ≤ p) :
  a ^ p + b ^ p ≤ 1 :=
begin
  have h_le_one : ∀ x : ℝ≥0∞, x ≤ 1 → x ^ p ≤ x, from λ x hx, rpow_le_self_of_le_one hx hp1,
  have ha : a ≤ 1, from (self_le_add_right a b).trans hab,
  have hb : b ≤ 1, from (self_le_add_left b a).trans hab,
  exact (add_le_add (h_le_one a ha) (h_le_one b hb)).trans hab,
end

lemma add_rpow_le_rpow_add {p : ℝ} (a b : ℝ≥0∞) (hp1 : 1 ≤ p) :
  a ^ p + b ^ p ≤ (a + b) ^ p :=
begin
  have hp_pos : 0 < p := lt_of_lt_of_le zero_lt_one hp1,
  by_cases h_top : a + b = ⊤,
  { rw ←@ennreal.rpow_eq_top_iff_of_pos (a + b) p hp_pos at h_top,
    rw h_top,
    exact le_top, },
  obtain ⟨ha_top, hb_top⟩ := add_ne_top.mp h_top,
  by_cases h_zero : a + b = 0,
  { simp [add_eq_zero_iff.mp h_zero, ennreal.zero_rpow_of_pos hp_pos], },
  have h_nonzero : ¬(a = 0 ∧ b = 0), by rwa add_eq_zero_iff at h_zero,
  have h_add : a/(a+b) + b/(a+b) = 1, by rw [div_add_div_same, div_self h_zero h_top],
  have h := add_rpow_le_one_of_add_le_one (a/(a+b)) (b/(a+b)) h_add.le hp1,
  rw [div_rpow_of_nonneg a (a+b) hp_pos.le, div_rpow_of_nonneg b (a+b) hp_pos.le] at h,
  have hab_0 : (a + b)^p ≠ 0, by simp [ha_top, hb_top, hp_pos, h_nonzero],
  have hab_top : (a + b)^p ≠ ⊤, by simp [ha_top, hb_top, hp_pos, h_nonzero],
  have h_mul : (a + b)^p * (a ^ p / (a + b) ^ p + b ^ p / (a + b) ^ p) ≤ (a + b)^p,
  { nth_rewrite 3 ←mul_one ((a + b)^p),
    exact (mul_le_mul_left hab_0 hab_top).mpr h, },
  rwa [div_eq_mul_inv, div_eq_mul_inv, mul_add, mul_comm (a^p), mul_comm (b^p), ←mul_assoc,
    ←mul_assoc, mul_inv_cancel hab_0 hab_top, one_mul, one_mul] at h_mul,
end

lemma rpow_add_rpow_le_add {p : ℝ} (a b : ℝ≥0∞) (hp1 : 1 ≤ p) :
  (a ^ p + b ^ p) ^ (1/p) ≤ a + b :=
begin
  rw ←@ennreal.le_rpow_one_div_iff _ _ (1/p) (by simp [lt_of_lt_of_le zero_lt_one hp1]),
  rw one_div_one_div,
  exact add_rpow_le_rpow_add _ _ hp1,
end

theorem rpow_add_rpow_le {p q : ℝ} (a b : ℝ≥0∞) (hp_pos : 0 < p) (hpq : p ≤ q) :
  (a ^ q + b ^ q) ^ (1/q) ≤ (a ^ p + b ^ p) ^ (1/p) :=
begin
  have h_rpow : ∀ a : ℝ≥0∞, a^q = (a^p)^(q/p),
    from λ a, by rw [←ennreal.rpow_mul, div_eq_inv_mul, ←mul_assoc,
      _root_.mul_inv_cancel hp_pos.ne.symm, one_mul],
  have h_rpow_add_rpow_le_add : ((a^p)^(q/p) + (b^p)^(q/p)) ^ (1/(q/p)) ≤ a^p + b^p,
  { refine rpow_add_rpow_le_add (a^p) (b^p) _,
    rwa one_le_div hp_pos, },
  rw [h_rpow a, h_rpow b, ennreal.le_rpow_one_div_iff hp_pos, ←ennreal.rpow_mul, mul_comm,
    mul_one_div],
  rwa one_div_div at h_rpow_add_rpow_le_add,
end

lemma rpow_add_le_add_rpow {p : ℝ} (a b : ℝ≥0∞) (hp_pos : 0 < p) (hp1 : p ≤ 1) :
  (a + b) ^ p ≤ a ^ p + b ^ p :=
begin
  have h := rpow_add_rpow_le a b hp_pos hp1,
  rw one_div_one at h,
  repeat { rw ennreal.rpow_one at h },
  exact (ennreal.le_rpow_one_div_iff hp_pos).mp h,
end

end ennreal

section lintegral
/-!
### Hölder's inequality for the Lebesgue integral of ℝ≥0∞ and nnreal functions

We prove `∫ (f * g) ∂μ ≤ (∫ f^p ∂μ) ^ (1/p) * (∫ g^q ∂μ) ^ (1/q)` for `p`, `q`
conjugate real exponents and `α→(e)nnreal` functions in several cases, the first two being useful
only to prove the more general results:
* `ennreal.lintegral_mul_le_one_of_lintegral_rpow_eq_one` : ℝ≥0∞ functions for which the
    integrals on the right are equal to 1,
* `ennreal.lintegral_mul_le_Lp_mul_Lq_of_ne_zero_of_ne_top` : ℝ≥0∞ functions for which the
    integrals on the right are neither ⊤ nor 0,
* `ennreal.lintegral_mul_le_Lp_mul_Lq` : ℝ≥0∞ functions,
* `nnreal.lintegral_mul_le_Lp_mul_Lq`  : nnreal functions.
-/

open measure_theory
variables {α : Type*} [measurable_space α] {μ : measure α}

namespace ennreal

lemma lintegral_mul_le_one_of_lintegral_rpow_eq_one {p q : ℝ} (hpq : p.is_conjugate_exponent q)
  {f g : α → ℝ≥0∞} (hf : ae_measurable f μ) (hg : ae_measurable g μ)
  (hf_norm : ∫⁻ a, (f a)^p ∂μ = 1) (hg_norm : ∫⁻ a, (g a)^q ∂μ = 1) :
  ∫⁻ a, (f * g) a ∂μ ≤ 1 :=
begin
  calc ∫⁻ (a : α), ((f * g) a) ∂μ
      ≤ ∫⁻ (a : α), ((f a)^p / ennreal.of_real p + (g a)^q / ennreal.of_real q) ∂μ :
    lintegral_mono (λ a, young_inequality (f a) (g a) hpq)
  ... = 1 :
  begin
    simp only [div_eq_mul_inv],
    rw lintegral_add',
    { rw [lintegral_mul_const'' _ (hf.pow_const p), lintegral_mul_const'' _ (hg.pow_const q),
        hf_norm, hg_norm, ← div_eq_mul_inv, ← div_eq_mul_inv, hpq.inv_add_inv_conj_ennreal], },
    { exact (hf.pow_const _).mul_const _, },
    { exact (hg.pow_const _).mul_const _, },
  end
end

/-- Function multiplied by the inverse of its p-seminorm `(∫⁻ f^p ∂μ) ^ 1/p`-/
def fun_mul_inv_snorm (f : α → ℝ≥0∞) (p : ℝ) (μ : measure α) : α → ℝ≥0∞ :=
λ a, (f a) * ((∫⁻ c, (f c) ^ p ∂μ) ^ (1 / p))⁻¹

lemma fun_eq_fun_mul_inv_snorm_mul_snorm {p : ℝ} (f : α → ℝ≥0∞)
  (hf_nonzero : ∫⁻ a, (f a) ^ p ∂μ ≠ 0) (hf_top : ∫⁻ a, (f a) ^ p ∂μ ≠ ⊤) {a : α} :
  f a = (fun_mul_inv_snorm f p μ a) * (∫⁻ c, (f c)^p ∂μ)^(1/p) :=
by simp [fun_mul_inv_snorm, mul_assoc, inv_mul_cancel, hf_nonzero, hf_top]

lemma fun_mul_inv_snorm_rpow {p : ℝ} (hp0 : 0 < p) {f : α → ℝ≥0∞} {a : α} :
  (fun_mul_inv_snorm f p μ a) ^ p = (f a)^p * (∫⁻ c, (f c) ^ p ∂μ)⁻¹ :=
begin
  rw [fun_mul_inv_snorm, mul_rpow_of_nonneg _ _ (le_of_lt hp0)],
  suffices h_inv_rpow : ((∫⁻ (c : α), f c ^ p ∂μ) ^ (1 / p))⁻¹ ^ p = (∫⁻ (c : α), f c ^ p ∂μ)⁻¹,
  by rw h_inv_rpow,
  rw [inv_rpow_of_pos hp0, ←rpow_mul, div_eq_mul_inv, one_mul,
    _root_.inv_mul_cancel (ne_of_lt hp0).symm, rpow_one],
end

lemma lintegral_rpow_fun_mul_inv_snorm_eq_one {p : ℝ} (hp0_lt : 0 < p) {f : α → ℝ≥0∞}
  (hf : ae_measurable f μ) (hf_nonzero : ∫⁻ a, (f a)^p ∂μ ≠ 0) (hf_top : ∫⁻ a, (f a)^p ∂μ ≠ ⊤) :
  ∫⁻ c, (fun_mul_inv_snorm f p μ c)^p ∂μ = 1 :=
begin
  simp_rw fun_mul_inv_snorm_rpow hp0_lt,
  rw [lintegral_mul_const'' _ (hf.pow_const p), mul_inv_cancel hf_nonzero hf_top],
end

/-- Hölder's inequality in case of finite non-zero integrals -/
lemma lintegral_mul_le_Lp_mul_Lq_of_ne_zero_of_ne_top {p q : ℝ} (hpq : p.is_conjugate_exponent q)
  {f g : α → ℝ≥0∞} (hf : ae_measurable f μ) (hg : ae_measurable g μ)
  (hf_nontop : ∫⁻ a, (f a)^p ∂μ ≠ ⊤) (hg_nontop : ∫⁻ a, (g a)^q ∂μ ≠ ⊤)
  (hf_nonzero : ∫⁻ a, (f a)^p ∂μ ≠ 0) (hg_nonzero : ∫⁻ a, (g a)^q ∂μ ≠ 0) :
  ∫⁻ a, (f * g) a ∂μ ≤ (∫⁻ a, (f a)^p ∂μ)^(1/p) * (∫⁻ a, (g a)^q ∂μ)^(1/q) :=
begin
  let npf := (∫⁻ (c : α), (f c) ^ p ∂μ) ^ (1/p),
  let nqg := (∫⁻ (c : α), (g c) ^ q ∂μ) ^ (1/q),
  calc ∫⁻ (a : α), (f * g) a ∂μ
    = ∫⁻ (a : α), ((fun_mul_inv_snorm f p μ * fun_mul_inv_snorm g q μ) a)
      * (npf * nqg) ∂μ :
  begin
    refine lintegral_congr (λ a, _),
    rw [pi.mul_apply, fun_eq_fun_mul_inv_snorm_mul_snorm f hf_nonzero hf_nontop,
      fun_eq_fun_mul_inv_snorm_mul_snorm g hg_nonzero hg_nontop, pi.mul_apply],
    ring,
  end
  ... ≤ npf * nqg :
  begin
    rw lintegral_mul_const' (npf * nqg) _ (by simp [hf_nontop, hg_nontop, hf_nonzero, hg_nonzero]),
    nth_rewrite 1 ←one_mul (npf * nqg),
    refine mul_le_mul _ (le_refl (npf * nqg)),
    have hf1 := lintegral_rpow_fun_mul_inv_snorm_eq_one hpq.pos hf hf_nonzero hf_nontop,
    have hg1 := lintegral_rpow_fun_mul_inv_snorm_eq_one hpq.symm.pos hg hg_nonzero hg_nontop,
    exact lintegral_mul_le_one_of_lintegral_rpow_eq_one hpq (hf.mul_const _)
      (hg.mul_const _) hf1 hg1,
  end
end

lemma ae_eq_zero_of_lintegral_rpow_eq_zero {p : ℝ} (hp0_lt : 0 < p) {f : α → ℝ≥0∞}
  (hf : ae_measurable f μ) (hf_zero : ∫⁻ a, (f a)^p ∂μ = 0) :
  f =ᵐ[μ] 0 :=
begin
  rw lintegral_eq_zero_iff' (hf.pow_const p) at hf_zero,
  refine filter.eventually.mp hf_zero (filter.eventually_of_forall (λ x, _)),
  dsimp only,
  rw [pi.zero_apply, rpow_eq_zero_iff],
  intro hx,
  cases hx,
  { exact hx.left, },
  { exfalso,
    linarith, },
end

lemma lintegral_mul_eq_zero_of_lintegral_rpow_eq_zero {p : ℝ} (hp0_lt : 0 < p)
  {f g : α → ℝ≥0∞} (hf : ae_measurable f μ) (hf_zero : ∫⁻ a, (f a)^p ∂μ = 0) :
  ∫⁻ a, (f * g) a ∂μ = 0 :=
begin
  rw ←@lintegral_zero_fun α _ μ,
  refine lintegral_congr_ae _,
  suffices h_mul_zero : f * g =ᵐ[μ] 0 * g , by rwa zero_mul at h_mul_zero,
  have hf_eq_zero : f =ᵐ[μ] 0, from ae_eq_zero_of_lintegral_rpow_eq_zero hp0_lt hf hf_zero,
  exact filter.eventually_eq.mul hf_eq_zero (ae_eq_refl g),
end

lemma lintegral_mul_le_Lp_mul_Lq_of_ne_zero_of_eq_top {p q : ℝ} (hp0_lt : 0 < p) (hq0 : 0 ≤ q)
  {f g : α → ℝ≥0∞} (hf_top : ∫⁻ a, (f a)^p ∂μ = ⊤) (hg_nonzero : ∫⁻ a, (g a)^q ∂μ ≠ 0) :
  ∫⁻ a, (f * g) a ∂μ ≤ (∫⁻ a, (f a)^p ∂μ) ^ (1/p) * (∫⁻ a, (g a)^q ∂μ) ^ (1/q) :=
begin
  refine le_trans le_top (le_of_eq _),
  have hp0_inv_lt : 0 < 1/p, by simp [hp0_lt],
  rw [hf_top, ennreal.top_rpow_of_pos hp0_inv_lt],
  simp [hq0, hg_nonzero],
end

/-- Hölder's inequality for functions `α → ℝ≥0∞`. The integral of the product of two functions
is bounded by the product of their `ℒp` and `ℒq` seminorms when `p` and `q` are conjugate
exponents. -/
theorem lintegral_mul_le_Lp_mul_Lq (μ : measure α) {p q : ℝ} (hpq : p.is_conjugate_exponent q)
  {f g : α → ℝ≥0∞} (hf : ae_measurable f μ) (hg : ae_measurable g μ) :
  ∫⁻ a, (f * g) a ∂μ ≤ (∫⁻ a, (f a)^p ∂μ) ^ (1/p) * (∫⁻ a, (g a)^q ∂μ) ^ (1/q) :=
begin
  by_cases hf_zero : ∫⁻ a, (f a) ^ p ∂μ = 0,
  { refine le_trans (le_of_eq _) (zero_le _),
    exact lintegral_mul_eq_zero_of_lintegral_rpow_eq_zero hpq.pos hf hf_zero, },
  by_cases hg_zero : ∫⁻ a, (g a) ^ q ∂μ = 0,
  { refine le_trans (le_of_eq _) (zero_le _),
    rw mul_comm,
    exact lintegral_mul_eq_zero_of_lintegral_rpow_eq_zero hpq.symm.pos hg hg_zero, },
  by_cases hf_top : ∫⁻ a, (f a) ^ p ∂μ = ⊤,
  { exact lintegral_mul_le_Lp_mul_Lq_of_ne_zero_of_eq_top hpq.pos hpq.symm.nonneg hf_top hg_zero, },
  by_cases hg_top : ∫⁻ a, (g a) ^ q ∂μ = ⊤,
  { rw [mul_comm, mul_comm ((∫⁻ (a : α), (f a) ^ p ∂μ) ^ (1 / p))],
    exact lintegral_mul_le_Lp_mul_Lq_of_ne_zero_of_eq_top hpq.symm.pos hpq.nonneg hg_top hf_zero, },
  -- non-⊤ non-zero case
  exact ennreal.lintegral_mul_le_Lp_mul_Lq_of_ne_zero_of_ne_top hpq hf hg hf_top hg_top hf_zero
    hg_zero,
end

lemma lintegral_rpow_add_lt_top_of_lintegral_rpow_lt_top {p : ℝ}
  {f g : α → ℝ≥0∞} (hf : ae_measurable f μ) (hf_top : ∫⁻ a, (f a) ^ p ∂μ < ⊤)
  (hg : ae_measurable g μ) (hg_top : ∫⁻ a, (g a) ^ p ∂μ < ⊤) (hp1 : 1 ≤ p) :
  ∫⁻ a, ((f + g) a) ^ p ∂μ < ⊤ :=
begin
  have hp0_lt : 0 < p, from lt_of_lt_of_le zero_lt_one hp1,
  have hp0 : 0 ≤ p, from le_of_lt hp0_lt,
  calc ∫⁻ (a : α), (f a + g a) ^ p ∂μ
    ≤ ∫⁻ a, ((2:ℝ≥0∞)^(p-1) * (f a) ^ p + (2:ℝ≥0∞)^(p-1) * (g a) ^ p) ∂ μ :
  begin
    refine lintegral_mono (λ a, _),
    dsimp only,
    have h_zero_lt_half_rpow : (0 : ℝ≥0∞) < (1 / 2) ^ p,
    { rw [←ennreal.zero_rpow_of_pos hp0_lt],
      exact ennreal.rpow_lt_rpow (by simp [zero_lt_one]) hp0_lt, },
    have h_rw : (1 / 2) ^ p * (2:ℝ≥0∞) ^ (p - 1) = 1 / 2,
    { rw [sub_eq_add_neg, ennreal.rpow_add _ _ ennreal.two_ne_zero ennreal.coe_ne_top,
        ←mul_assoc, ←ennreal.mul_rpow_of_nonneg _ _ hp0, one_div,
        ennreal.inv_mul_cancel ennreal.two_ne_zero ennreal.coe_ne_top, ennreal.one_rpow,
        one_mul, ennreal.rpow_neg_one], },
    rw ←ennreal.mul_le_mul_left (ne_of_lt h_zero_lt_half_rpow).symm _,
    { rw [mul_add, ← mul_assoc, ← mul_assoc, h_rw, ←ennreal.mul_rpow_of_nonneg _ _ hp0, mul_add],
      refine ennreal.rpow_arith_mean_le_arith_mean2_rpow (1/2 : ℝ≥0∞) (1/2 : ℝ≥0∞)
        (f a) (g a) _ hp1,
      rw [ennreal.div_add_div_same, one_add_one_eq_two,
        ennreal.div_self ennreal.two_ne_zero ennreal.coe_ne_top], },
    { rw ←ennreal.lt_top_iff_ne_top,
      refine ennreal.rpow_lt_top_of_nonneg hp0 _,
      rw [one_div, ennreal.inv_ne_top],
      exact ennreal.two_ne_zero, },
  end
  ... < ⊤ :
  begin
    rw [lintegral_add', lintegral_const_mul'' _ (hf.pow_const p),
      lintegral_const_mul'' _ (hg.pow_const p), ennreal.add_lt_top],
    { have h_two : (2 : ℝ≥0∞) ^ (p - 1) < ⊤,
      from ennreal.rpow_lt_top_of_nonneg (by simp [hp1]) ennreal.coe_ne_top,
      repeat {rw ennreal.mul_lt_top_iff},
      simp [hf_top, hg_top, h_two], },
    { exact (hf.pow_const _).const_mul _ },
    { exact (hg.pow_const _).const_mul _ },
  end
end

lemma lintegral_Lp_mul_le_Lq_mul_Lr {α} [measurable_space α] {p q r : ℝ} (hp0_lt : 0 < p)
  (hpq : p < q) (hpqr : 1/p = 1/q + 1/r) (μ : measure α) {f g : α → ℝ≥0∞}
  (hf : ae_measurable f μ) (hg : ae_measurable g μ) :
  (∫⁻ a, ((f * g) a)^p ∂μ) ^ (1/p) ≤ (∫⁻ a, (f a)^q ∂μ) ^ (1/q) * (∫⁻ a, (g a)^r ∂μ) ^ (1/r) :=
begin
  have hp0_ne : p ≠ 0, from (ne_of_lt hp0_lt).symm,
  have hp0 : 0 ≤ p, from le_of_lt hp0_lt,
  have hq0_lt : 0 < q, from lt_of_le_of_lt hp0 hpq,
  have hq0_ne : q ≠ 0, from (ne_of_lt hq0_lt).symm,
  have h_one_div_r : 1/r = 1/p - 1/q, by simp [hpqr],
  have hr0_ne : r ≠ 0,
  { have hr_inv_pos : 0 < 1/r,
    by rwa [h_one_div_r, sub_pos, one_div_lt_one_div hq0_lt hp0_lt],
    rw [one_div, _root_.inv_pos] at hr_inv_pos,
    exact (ne_of_lt hr_inv_pos).symm, },
  let p2 := q/p,
  let q2 := p2.conjugate_exponent,
  have hp2q2 : p2.is_conjugate_exponent q2,
  from real.is_conjugate_exponent_conjugate_exponent (by simp [lt_div_iff, hpq, hp0_lt]),
  calc (∫⁻ (a : α), ((f * g) a) ^ p ∂μ) ^ (1 / p)
      = (∫⁻ (a : α), (f a)^p * (g a)^p ∂μ) ^ (1 / p) :
  by simp_rw [pi.mul_apply, ennreal.mul_rpow_of_nonneg _ _ hp0]
  ... ≤ ((∫⁻ a, (f a)^(p * p2) ∂ μ)^(1/p2) * (∫⁻ a, (g a)^(p * q2) ∂ μ)^(1/q2)) ^ (1/p) :
  begin
    refine ennreal.rpow_le_rpow _ (by simp [hp0]),
    simp_rw ennreal.rpow_mul,
    exact ennreal.lintegral_mul_le_Lp_mul_Lq μ hp2q2 (hf.pow_const _) (hg.pow_const _)
  end
  ... = (∫⁻ (a : α), (f a) ^ q ∂μ) ^ (1 / q) * (∫⁻ (a : α), (g a) ^ r ∂μ) ^ (1 / r) :
  begin
    rw [@ennreal.mul_rpow_of_nonneg _ _ (1/p) (by simp [hp0]), ←ennreal.rpow_mul,
      ←ennreal.rpow_mul],
    have hpp2 : p * p2 = q,
    { symmetry, rw [mul_comm, ←div_eq_iff hp0_ne], },
    have hpq2 : p * q2 = r,
    { rw [← inv_inv' r, ← one_div, ← one_div, h_one_div_r],
      field_simp [q2, real.conjugate_exponent, p2, hp0_ne, hq0_ne] },
    simp_rw [div_mul_div, mul_one, mul_comm p2, mul_comm q2, hpp2, hpq2],
  end
end

lemma lintegral_mul_rpow_le_lintegral_rpow_mul_lintegral_rpow {p q : ℝ}
  (hpq : p.is_conjugate_exponent q) {f g : α → ℝ≥0∞}
  (hf : ae_measurable f μ) (hg : ae_measurable g μ) (hf_top : ∫⁻ a, (f a) ^ p ∂μ ≠ ⊤) :
  ∫⁻ a, (f a) * (g a) ^ (p - 1) ∂μ ≤ (∫⁻ a, (f a)^p ∂μ) ^ (1/p) * (∫⁻ a, (g a)^p ∂μ) ^ (1/q) :=
begin
  refine le_trans (ennreal.lintegral_mul_le_Lp_mul_Lq μ hpq hf (hg.pow_const _)) _,
  by_cases hf_zero_rpow : (∫⁻ (a : α), (f a) ^ p ∂μ) ^ (1 / p) = 0,
  { rw [hf_zero_rpow, zero_mul],
    exact zero_le _, },
  have hf_top_rpow : (∫⁻ (a : α), (f a) ^ p ∂μ) ^ (1 / p) ≠ ⊤,
  { by_contra h,
    push_neg at h,
    refine hf_top _,
    have hp_not_neg : ¬ p < 0, by simp [hpq.nonneg],
    simpa [hpq.pos, hp_not_neg] using h, },
  refine (ennreal.mul_le_mul_left hf_zero_rpow hf_top_rpow).mpr (le_of_eq _),
  congr,
  ext1 a,
  rw [←ennreal.rpow_mul, hpq.sub_one_mul_conj],
end

lemma lintegral_rpow_add_le_add_snorm_mul_lintegral_rpow_add {p q : ℝ}
  (hpq : p.is_conjugate_exponent q) {f g : α → ℝ≥0∞} (hf : ae_measurable f μ)
  (hf_top : ∫⁻ a, (f a) ^ p ∂μ ≠ ⊤) (hg : ae_measurable g μ) (hg_top : ∫⁻ a, (g a) ^ p ∂μ ≠ ⊤) :
  ∫⁻ a, ((f + g) a)^p ∂ μ
    ≤ ((∫⁻ a, (f a)^p ∂μ) ^ (1/p) + (∫⁻ a, (g a)^p ∂μ) ^ (1/p))
      * (∫⁻ a, (f a + g a)^p ∂μ) ^ (1/q) :=
begin
  calc ∫⁻ a, ((f+g) a) ^ p ∂μ ≤ ∫⁻ a, ((f + g) a) * ((f + g) a) ^ (p - 1) ∂μ :
  begin
    refine lintegral_mono (λ a, _),
    dsimp only,
    by_cases h_zero : (f + g) a = 0,
    { rw [h_zero, ennreal.zero_rpow_of_pos hpq.pos],
      exact zero_le _, },
    by_cases h_top : (f + g) a = ⊤,
    { rw [h_top, ennreal.top_rpow_of_pos hpq.sub_one_pos, ennreal.top_mul_top],
      exact le_top, },
    refine le_of_eq _,
    nth_rewrite 1 ←ennreal.rpow_one ((f + g) a),
    rw [←ennreal.rpow_add _ _ h_zero h_top, add_sub_cancel'_right],
  end
    ... = ∫⁻ (a : α), f a * (f + g) a ^ (p - 1) ∂μ + ∫⁻ (a : α), g a * (f + g) a ^ (p - 1) ∂μ :
  begin
    have h_add_m : ae_measurable (λ (a : α), ((f + g) a) ^ (p-1)) μ,
      from (hf.add hg).pow_const _,
    have h_add_apply : ∫⁻ (a : α), (f + g) a * (f + g) a ^ (p - 1) ∂μ
      = ∫⁻ (a : α), (f a + g a) * (f + g) a ^ (p - 1) ∂μ,
    from rfl,
    simp_rw [h_add_apply, add_mul],
    rw lintegral_add' (hf.mul h_add_m) (hg.mul h_add_m),
  end
    ... ≤ ((∫⁻ a, (f a)^p ∂μ) ^ (1/p) + (∫⁻ a, (g a)^p ∂μ) ^ (1/p))
      * (∫⁻ a, (f a + g a)^p ∂μ) ^ (1/q) :
  begin
    rw add_mul,
    exact add_le_add
      (lintegral_mul_rpow_le_lintegral_rpow_mul_lintegral_rpow hpq hf (hf.add hg) hf_top)
      (lintegral_mul_rpow_le_lintegral_rpow_mul_lintegral_rpow hpq hg (hf.add hg) hg_top),
  end
end

private lemma lintegral_Lp_add_le_aux {p q : ℝ}
  (hpq : p.is_conjugate_exponent q) {f g : α → ℝ≥0∞} (hf : ae_measurable f μ)
  (hf_top : ∫⁻ a, (f a) ^ p ∂μ ≠ ⊤) (hg : ae_measurable g μ) (hg_top : ∫⁻ a, (g a) ^ p ∂μ ≠ ⊤)
  (h_add_zero : ∫⁻ a, ((f+g) a) ^ p ∂ μ ≠ 0) (h_add_top : ∫⁻ a, ((f+g) a) ^ p ∂ μ ≠ ⊤) :
  (∫⁻ a, ((f + g) a)^p ∂ μ) ^ (1/p) ≤ (∫⁻ a, (f a)^p ∂μ) ^ (1/p) + (∫⁻ a, (g a)^p ∂μ) ^ (1/p) :=
begin
  have hp_not_nonpos : ¬ p ≤ 0, by simp [hpq.pos],
  have htop_rpow : (∫⁻ a, ((f+g) a) ^ p ∂μ)^(1/p) ≠ ⊤,
  { by_contra h,
    push_neg at h,
    exact h_add_top (@ennreal.rpow_eq_top_of_nonneg _ (1/p) (by simp [hpq.nonneg]) h), },
  have h0_rpow : (∫⁻ a, ((f+g) a) ^ p ∂ μ) ^ (1/p) ≠ 0,
  by simp [h_add_zero, h_add_top, hpq.nonneg, hp_not_nonpos, -pi.add_apply],
  suffices h : 1 ≤ (∫⁻ (a : α), ((f+g) a)^p ∂μ) ^ -(1/p)
    * ((∫⁻ (a : α), (f a)^p ∂μ) ^ (1/p) + (∫⁻ (a : α), (g a)^p ∂μ) ^ (1/p)),
  by rwa [←mul_le_mul_left h0_rpow htop_rpow, ←mul_assoc, ←rpow_add _ _ h_add_zero h_add_top,
    ←sub_eq_add_neg, _root_.sub_self, rpow_zero, one_mul, mul_one] at h,
  have h : ∫⁻ (a : α), ((f+g) a)^p ∂μ
    ≤ ((∫⁻ (a : α), (f a)^p ∂μ) ^ (1/p) + (∫⁻ (a : α), (g a)^p ∂μ) ^ (1/p))
      * (∫⁻ (a : α), ((f+g) a)^p ∂μ) ^ (1/q),
  from lintegral_rpow_add_le_add_snorm_mul_lintegral_rpow_add hpq hf hf_top hg hg_top,
  have h_one_div_q : 1/q = 1 - 1/p, by { nth_rewrite 1 ←hpq.inv_add_inv_conj, ring, },
  simp_rw [h_one_div_q, sub_eq_add_neg 1 (1/p), ennreal.rpow_add _ _ h_add_zero h_add_top,
    rpow_one] at h,
  nth_rewrite 1 mul_comm at h,
  nth_rewrite 0 ←one_mul (∫⁻ (a : α), ((f+g) a) ^ p ∂μ) at h,
  rwa [←mul_assoc, ennreal.mul_le_mul_right h_add_zero h_add_top, mul_comm] at h,
end

/-- Minkowski's inequality for functions `α → ℝ≥0∞`: the `ℒp` seminorm of the sum of two
functions is bounded by the sum of their `ℒp` seminorms. -/
theorem lintegral_Lp_add_le {p : ℝ} {f g : α → ℝ≥0∞}
  (hf : ae_measurable f μ) (hg : ae_measurable g μ) (hp1 : 1 ≤ p) :
  (∫⁻ a, ((f + g) a)^p ∂ μ) ^ (1/p) ≤ (∫⁻ a, (f a)^p ∂μ) ^ (1/p) + (∫⁻ a, (g a)^p ∂μ) ^ (1/p) :=
begin
  have hp_pos : 0 < p, from lt_of_lt_of_le zero_lt_one hp1,
  by_cases hf_top : ∫⁻ a, (f a) ^ p ∂μ = ⊤,
  { simp [hf_top, hp_pos], },
  by_cases hg_top : ∫⁻ a, (g a) ^ p ∂μ = ⊤,
  { simp [hg_top, hp_pos], },
  by_cases h1 : p = 1,
  { refine le_of_eq _,
    simp_rw [h1, one_div_one, ennreal.rpow_one],
    exact lintegral_add' hf hg, },
  have hp1_lt : 1 < p, by { refine lt_of_le_of_ne hp1 _, symmetry, exact h1, },
  have hpq := real.is_conjugate_exponent_conjugate_exponent hp1_lt,
  by_cases h0 : ∫⁻ a, ((f+g) a) ^ p ∂ μ = 0,
  { rw [h0, @ennreal.zero_rpow_of_pos (1/p) (by simp [lt_of_lt_of_le zero_lt_one hp1])],
    exact zero_le _, },
  have htop : ∫⁻ a, ((f+g) a) ^ p ∂ μ ≠ ⊤,
  { rw ←ne.def at hf_top hg_top,
    rw ←ennreal.lt_top_iff_ne_top at hf_top hg_top ⊢,
    exact lintegral_rpow_add_lt_top_of_lintegral_rpow_lt_top hf hf_top hg hg_top hp1, },
  exact lintegral_Lp_add_le_aux hpq hf hf_top hg hg_top h0 htop,
end

end ennreal

/-- Hölder's inequality for functions `α → ℝ≥0`. The integral of the product of two functions
is bounded by the product of their `ℒp` and `ℒq` seminorms when `p` and `q` are conjugate
exponents. -/
theorem nnreal.lintegral_mul_le_Lp_mul_Lq {p q : ℝ} (hpq : p.is_conjugate_exponent q)
  {f g : α → ℝ≥0} (hf : ae_measurable f μ) (hg : ae_measurable g μ) :
  ∫⁻ a, (f * g) a ∂μ ≤ (∫⁻ a, (f a)^p ∂μ)^(1/p) * (∫⁻ a, (g a)^q ∂μ)^(1/q) :=
begin
  simp_rw [pi.mul_apply, ennreal.coe_mul],
  exact ennreal.lintegral_mul_le_Lp_mul_Lq μ hpq hf.ennreal_coe hg.ennreal_coe,
end

end lintegral


namespace mynat

inductive mynat : Type
| zero : mynat
| succ : mynat → mynat

def zero := mynat.zero
def one := mynat.succ zero
def two := mynat.succ one
def three :=
    mynat.succ
        (mynat.succ
            (mynat.succ
                zero))

#reduce one
#reduce two
#reduce three

/-
Unary functions
-/

-- identity function on mynat
def id_mynat (n: mynat) := n

-- constant zero function
def zero_mynat (n: mynat) := zero

-- predecessor function
def pred (n : mynat) :=
    match n with
    | mynat.zero := zero
    | mynat.succ n' := n'
    end

#reduce pred three
#reduce pred zero

/-mynat.succ (admynat.succ (add_mynat n m)mynat.succ (add_mynat n m)mynat.succ (add_mynat n m)mynat.succ (add_mynat n m)mynat.succ (add_mynat n m)mynat.succ (add_mynat n m)mynat.succ (add_mynat n m)d_mynat n m)
There are two new and important
concepts here. The first is a new
kind of matching. The second is
that we define the predecessor
of zero to be zero. That's a bit
odd.

Look at the matching. The first
pattern is familar. The second,
though, it interesting. If we get
here, we know n isn't zero, so it
must by the successor of the next
smaller mynat. Here we give that
next smaller value the name n'.
And of course that's the number
we want to return as precessor!
-/

/-
Now let's see binary operations
and recursive functions. To
define a recursive function we
have a new syntax/
-/

def add_mynat: mynat → mynat → mynat
| mynat.zero m := m
| (mynat.succ n') m :=
    mynat.succ (add_mynat n' m)

#reduce add_mynat three two

/-
Syntax notes: use explicit function
type syntax. Omit any :=. Define how
the function works by cases. Each case
defines what the function does for the
specific kinds of arguments used in the
case. Omit commas between arguments
All cases may be covered
-/

-- It works!
#reduce add_mynat one two
#reduce add_mynat three three

/-
EXERCISES:

(1) We just implemented addition as
the recursive (iterated) application
of the successor function. Now you are
to implement multiplication as iterated
addition.

(2) Implement exponentiation as iterated
multiplication.

(3) Take this pattern one step further.
What function did you implement? How
would you write it in regular math
notation?
-/

def mul_mynat: mynat → mynat → mynat
| mynat.zero m := zero
| (mynat.succ n') m := add_mynat m (mul_mynat n' m)

#reduce mul_mynat three two
#reduce mul_mynat three three

def exp_mynat: mynat → mynat → mynat
| n mynat.zero := mynat.succ mynat.zero
| n (mynat.succ m) := mul_mynat n (exp_mynat n  m)

#reduce exp_mynat three two
#reduce exp_mynat three three
#reduce exp_mynat zero zero
example : exp_mynat three two = mul_mynat three three := rfl

/-
We can easily prove that for all m : ℕ,
add_mynat 0 m = m, because the definition
of add_mynat has a matching pattern (the
first one), which explains exactly how to
reduce a term with first argument zero.
-/

theorem zero_left_id:
    ∀ m : mynat,
        add_mynat mynat.zero m = m
:=
begin
intro m,
-- apply rfl,
simp [add_mynat],
end

/-
We just asked Lean to simplify the
goal using the "simplication rules"
specified by the two cases in the
definition of add_mynat. The result
is m = m, which Lean takes care of
with an automated application of rfl.
-/

/-
Unfortunately, we don't have such
a simplification rule when the zero
is on the right! For that we need a
whole new proof strategy: proof by
induction.
-/

theorem zero_right_id :
    ∀ m : mynat,
        add_mynat m mynat.zero = m
:=
begin
    intro m,
    induction m with m' h,

    -- base case
    apply rfl,
    --simp [add_mynat],

    -- inductive case
    simp [add_mynat],
    assumption,
end

lemma add_n_succ_m :
    ∀ n m : mynat,
        add_mynat n (mynat.succ m) =
        mynat.succ (add_mynat n m) :=
begin
intros n m,
induction n with n h,
    trivial,

    rw add_mynat,
    rw add_mynat,
    rw h,
end

/-
Property verification: our addition
operation is commutative!
-/
example :
    ∀ m n : mynat,
    add_mynat m n = add_mynat n m :=
begin
    intros m n,

    -- by induction on m
    induction m with m h,

    --base case: m = zero
    rw add_mynat,
    rw zero_right_id,

    -- inductive case:
    -- if true for m then true for succ m
    rw add_mynat,
    rw add_n_succ_m,

    -- rewrite using induction hypothesis!
    rw h,
end

/-
Proof by induction is proof by
case analysis on the *constructors*
for values of a given type, with the
huge added benefit of an induction
hypothesis.

So, as an example, if we show that
some predicate involving a natural
number, n, is true no matter what
*constructor* was used to "build"
n, then we've' shown the predicate to
be true for *all* (∀) values of n,
because there are no n other than
those that can be built by the given
constructors. Proving a proposition
is true for all constructors is thus
tantamount to showing that it is
true for all values, even if there
are infinitely many.

Let's think about the two
constructors for values of type
mynat. First, there is the zero
constructor.  That's the "base
case". Second, there's the succ
constructor. From a value, n,
it constructs a value, succ n.

Now if we prove the following two
cases, we're done:

(1) the predicate in question is
true when n is zero

(2) if the predicate is true for
any n, then it is true for succ n.

The reason we're done is that
there are no other possibilities
for n.

The set of values of an inductively
defined type is defined to be exactly
the set of values that can be built
by using the available constructors
any *finite* number of times, and
that there are no other values of
the given type.
-/

/-
EXERCISE: try this proof using
cases instead of induction. Cases
also does case analysis on the
constructors. Why does simple case
analysis fail?
-/

/-
EXERCISES: To Come Shortly
-/

end mynat


import algebra.group_power data.nat.basic data.nat.modeq

variables a e n N r : ℕ
open nat
--3. (the “rule of n” for various integers n). You know that a positive integer is even if and only if its last digit is even. This is because if N is a positive integer with last digit d, then N − d ends in zero so is a multiple of 10 and hence a multiple of 2, so N is even if and only if d is even. Let’s find some more rules for working out if a number is divisible by n for various other small values of n.

/-(i) Prove (either by induction or directly) that 10^e ≡ 1 mod 9 for all e.
Deduce that if N is any positive integer and the sum of the digits of N is s, then N − s is a multiple of 9.
Deduce that N is a multiple of 9 if and only if s is. Is 123456789 a multiple of 9?-/
theorem Q0803ia : 10^e ≡ 1 [MOD 9] :=
begin
intros,
induction e,
trivial,
rw nat.pow_succ,
have h : 10 ≡ 1 [MOD 9],
trivial,
apply modeq.trans (modeq.modeq_mul_right 10 e_ih) h,
end

-- sum of digits (thanks to kevin)
lemma digit_sum_aux (n : ℕ) : succ n / 10 < succ n := begin
  rw lt_succ_iff,
  cases n with d,refl,
  apply nat.div_le_of_le_mul,
  show (succ d) + 1  ≤ (1 + 1 + 8) * _,
  rw [add_mul,add_mul,one_mul,add_assoc],
  apply add_le_add_left _,
  apply nat.le_add_left,
end

def s : ℕ → ℕ
| 0 := 0
| (succ n) := if succ n < 10 then succ n else
  have succ n / 10 < succ n, from digit_sum_aux n,
  (succ n) % 10 + s ((succ n) / 10)

theorem Q0803ib (hp: a = s N) : 9 ∣ (N - a) :=
begin
rw hp,
split,
assumption',
sorry
end

theorem Q0803ic (hp : a = s N) : 9 ∣ N ↔ 9 ∣ a :=
begin
rw hp,
split,

sorry
end

theorem Q0803id : 9 ∣ 123456789 := sorry

--(ii) Deduce from (i) that N is a multiple of 3 if and only if s is.
theorem Q0803ii (hp : a = s N) : 3 ∣ N ↔ 3 ∣ a :=
begin
rw hp,
split,

sorry,
end

--(iii) Prove (either by induction or directly) that if r ∈ Z≥1 and e ≥ r then 10e ≡ 0 mod 2r. Deduce that if N is any positive integer and s is the number which is the last r digits of N, then N−s is a multiple of 2r. Deduce that N is a multiple of 2r if and only if the last r digits of N is a multiple of 2r. Is 3847534875634765124 a multiple of 4? Is 9995763848388 a multiple of 8?
theorem Q0803iii (hp : e ≥ r) : 10^e % 2*r = 0 := sorry

--(iv) Prove (either by induction or directly) that if e ∈ Z≥1 then 10e ≡ (−1)e mod 11. Deduce that if N is any positive integer and s is the number you get by doing (last digit of N) minus (second-last digit of N ) add (third-last digit of N ) minus. . . , then N − s is a multiple of 11. What is the remainder when 1234567 is divided by 11?
theorem Q0803iv : 10^e % 11 = (-1 : ℤ)^e := sorry


/-

Copyright (c) 2019 Neil Strickland. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Neil Strickland

-/

import data.fintype.basic
import data.fintype.card
import algebra.big_operators.basic
import algebra.big_operators.order
import tactic.squeeze

namespace combinatorics

universes u v
variables {α : Type u} {β : Type v} (p : α → β)
variables [fintype α] [fintype β] [decidable_eq α] [decidable_eq β]

def fiber (b : β) : Type* := { a : α // p a = b }

instance (b : β) : fintype (fiber p b) :=
 by { dsimp[fiber], apply_instance }

def fiber' (b : β) : finset α := finset.univ.filter (λ a, p a = b)

lemma mem_fiber' (b : β) (a : α) : a ∈ fiber' p b ↔ p a = b :=
 ⟨λ h,(finset.mem_filter.mp h).right,
  λ h,finset.mem_filter.mpr ⟨finset.mem_univ a,h⟩⟩

lemma card_fiber (b : β) : fintype.card (fiber p b) = (fiber' p b).card :=
 fintype.subtype_card (fiber' p b) (mem_fiber' p b)

def equiv_fibre_sigma : α ≃ Σ (b : β), (fiber p b) := {
  to_fun := λ a, ⟨p a,⟨a,rfl⟩⟩,
  inv_fun := λ x, x.2.val,
  left_inv := λ a, by { refl },
  right_inv := by { rintro ⟨b,⟨a,⟨e⟩⟩⟩, simp only[heq_iff_eq], split; refl }
}

lemma card_eq_fiber_sum :
 fintype.card α = finset.univ.sum (λ b, fintype.card (fiber p b)) :=
 (fintype.card_congr (equiv_fibre_sigma p)).trans (fintype.card_sigma (fiber p))

lemma card_eq_fiber_sum' :
 fintype.card α = finset.univ.sum (λ b, finset.card (fiber' p b)) :=
begin
 let e0 := card_eq_fiber_sum p,
 let e1 : ∀ b : β, b ∈ finset.univ → fintype.card (fiber p b) = finset.card (fiber' p b)
  := λ b _, card_fiber p b,
 let e2 := @finset.sum_congr ℕ β finset.univ _ _ _ _ rfl e1,
 exact e0.trans e2
end

variable {p}

lemma fiber_nonempty_of_surjective
 (p_surj : function.surjective p) (b : β) : nonempty (fiber p b) :=
begin
 rcases p_surj b with ⟨a,e⟩,
 exact ⟨⟨a,e⟩⟩,
end

lemma card_le_of_surjective :
 function.surjective p → (fintype.card β) ≤ (fintype.card α) :=
begin
 intro p_surj,
 have h0 : ∀ b, b ∈ finset.univ → 1 ≤ fintype.card (fiber p b) :=
  λ b _, fintype.card_pos_iff.mpr (fiber_nonempty_of_surjective p_surj b),
 let h1 := @finset.sum_le_sum β ℕ _ _ _ finset.univ h0,
 let h2 := calc
  finset.sum finset.univ (λ b : β, 1) =
   add_monoid.nsmul finset.univ.card 1 :
     @finset.sum_const ℕ β finset.univ _ 1
  ... = ↑finset.univ.card : nsmul_one _
  ... = finset.univ.card : nat.cast_id _
  ... = fintype.card β : rfl,
 rw[h2,← card_eq_fiber_sum p] at h1,
 exact h1
end

end combinatorics


variables {A B C D E F P Q R : Prop}

namespace theorems

	theorem or_simplification_right (A B : Prop) : (A ∨ B) ∧ ¬ B → A :=
	assume h1: (A ∨ B) ∧ ¬ B,
	show A, from or.elim (and.left h1)
	   (assume h: A, show A, from h)
	   (assume h: B, show A, from false.elim (and.right h1 h))

	theorem or_simplification_left (A B : Prop) : (A ∨ B) ∧ ¬ A → B :=
	assume h1: (A ∨ B) ∧ ¬ A,
	show B, from or.elim (and.left h1)
	   (assume h: A, show B, from false.elim (and.right h1 h))
	   (assume h: B, show B, from h)

	theorem and_simplification_left (A B: Prop) : ¬ (A ∧ B) ∧ A → ¬ B :=
	assume h: ¬ (A ∧ B) ∧ A,
	assume h1: B,
	show false, from h.left (and.intro h.right h1)

end theorems

-- exercício 1

variable h: ¬ A → false

example (h1: A ∨ ¬ A) : A :=
have h2: ¬¬ A, from
  assume a : ¬ A,
  show false, from h a,
show A, from theorems.or_simplification_right A (¬ A) (and.intro h1 h2)

-- exercício 2

example (h: ¬ A ∨ ¬ B): ¬ (A ∧ B) :=
show ¬ (A ∧ B), from or.elim h
    (assume h1: ¬ A, assume h2: A ∧ B, show false, from h1 (and.left h2))
    (assume h1: ¬ B, assume h2: A ∧ B, show false, from h1 (and.right h2))

open classical

-- exercício 3

example (h: ¬ (A ∧ B)): ¬ A ∨ ¬ B :=
show ¬ A ∨ ¬ B, from or.elim (em A)
    (assume h1: A, show ¬ A ∨ ¬ B, from
       or.inr (theorems.and_simplification_left A B (and.intro h h1)))
    (assume h1: ¬ A, show ¬ A ∨ ¬ B, from or.inl h1)

-- exercício 4

example (h1: ¬ P → (Q ∨ R)) (h2: ¬ Q) (h3: ¬ R): P :=
show P, from by_contradiction
    (assume h: ¬ P, show false,
        from h3 (theorems.or_simplification_left Q R (and.intro (h1 h) h2)))

-- exercício 5

example (h: A → B): ¬ A ∨ B :=
show ¬ A ∨ B, from or.elim (em A)
   (assume h1: A, show ¬ A ∨ B, from or.inr (h h1))
   (assume h1: ¬A, show ¬ A ∨ B, from or.inl h1)

-- exercício 6

example : A → ((A ∧ B) ∨ (A ∧ ¬ B)) :=
assume h: A,
show ((A ∧ B) ∨ (A ∧ ¬ B)), from or.elim (em B)
    (assume h1: B, show ((A ∧ B) ∨ (A ∧ ¬ B)), from or.inl (and.intro h h1))
    (assume h1: ¬B, show ((A ∧ B) ∨ (A ∧ ¬ B)), from or.inr (and.intro h h1))

-- exercício 7

-- Eu concluo que de (A ∨ B) ∧ (C ∨ D) ∧ (E ∨ F), chego em  (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨
-- (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨ (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F)
-- Mostrei como derivei essa afirmação, mesmo que não foi o pedido.

variable disjuntive: (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
                     (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F)

lemma step_6 (h: (A ∨ B) ∧ (C ∨ D) ∧ (E ∨ F)) (h1: B) (h2: D) :
        (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F) :=
show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
    from or.elim (h.right.right)
        (assume : E,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from or.inr (or.inr (or.inr (or.inr (or.inr (or.inr(or.inl (and.intro h1 (and.intro h2 this)))))))))
        (assume : F,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from or.inr (or.inr (or.inr (or.inr (or.inr (or.inr (or.inr (and.intro h1 (and.intro h2 this)))))))))

lemma step_5 (h: (A ∨ B) ∧ (C ∨ D) ∧ (E ∨ F)) (h1: B) (h2: C) :
        (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F) :=
show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
    from or.elim (h.right.right)
        (assume : E,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from or.inr (or.inr (or.inr (or.inr (or.inl (and.intro h1 (and.intro h2 this)))))))
        (assume : F,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from or.inr (or.inr (or.inr (or.inr (or.inr (or.inl (and.intro h1 (and.intro h2 this))))))))

lemma step_4 (h: (A ∨ B) ∧ (C ∨ D) ∧ (E ∨ F)) (h1: A) (h2: D) :
        (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F) :=
show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
    from or.elim (h.right.right)
        (assume : E,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from or.inr (or.inr (or.inl (and.intro h1 (and.intro h2 this)))))
        (assume : F,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from or.inr (or.inr (or.inr (or.inl (and.intro h1 (and.intro h2 this))))))

lemma step_3 (h: (A ∨ B) ∧ (C ∨ D) ∧ (E ∨ F)) (h1: A) (h2: C) :
        (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F) :=
show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
    from or.elim (h.right.right)
        (assume : E,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from or.inl (and.intro h1 (and.intro h2 this)))
        (assume : F,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from or.inr (or.inl (and.intro h1 (and.intro h2 this))))

lemma step_2 (h: (A ∨ B) ∧ (C ∨ D) ∧ (E ∨ F)) (h1: B) :
        (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F) :=
show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
    from or.elim (h.right.left)
        (assume : C,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from step_5 h h1 this)
        (assume : D,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from step_6 h h1 this)

lemma step_1 (h: (A ∨ B) ∧ (C ∨ D) ∧ (E ∨ F)) (h1: A) :
        (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F) :=
show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
    from or.elim (h.right.left)
        (assume : C,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from step_3 h h1 this)
        (assume : D,
        show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
            (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
            from step_4 h h1 this)

example (h: (A ∨ B) ∧ (C ∨ D) ∧ (E ∨ F)) :
        (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F) :=
show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
     (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
    from or.elim (h.left)
    (assume : A,
    show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
         (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
         from step_1 h this)
    (assume : B,
    show (A ∧ C ∧ E) ∨ (A ∧ C ∧ F) ∨ (A ∧ D ∧ E) ∨ (A ∧ D ∧ F) ∨
        (B ∧ C ∧ E) ∨ (B ∧ C ∧ F) ∨ (B ∧ D ∧ E) ∨ (B ∧ D ∧ F),
        from step_2 h this)

-- exercício 8

lemma step1 (h₁ : ¬ (A ∧ B)) (h₂ : A) : ¬ A ∨ ¬ B :=
have ¬ B, from
   assume : B,
   have A ∧ B, from (and.intro h₂ this),
   show false, from h₁ this,
show ¬ A ∨ ¬ B, from or.inr this

lemma step2 (h₁ : ¬ (A ∧ B)) (h₂ : ¬ (¬ A ∨ ¬ B)) : false :=
have ¬ A, from
  assume : A,
  have ¬ A ∨ ¬ B, from step1 h₁ ‹A›,
  show false, from h₂ this,
show false, from h₂ (or.inl ‹¬A›)

theorem step3 (h : ¬ (A ∧ B)) : ¬ A ∨ ¬ B :=
by_contradiction
  (assume h' : ¬ (¬ A ∨ ¬ B),
    show false, from step2 h h')

-- exercício 9

example (h : ¬ B → ¬ A) : A → B :=
assume h1: A,
show B, from or.elim (em B)
    (assume : B, show B, from this)
    (assume : ¬ B, show B, from by_contradiction
        (assume : ¬ B, show false, from (h this) h1))

example (h : A → B) : ¬ A ∨ B :=
show ¬ A ∨ B, from or.elim (em A)
   (assume h1: A, show ¬ A ∨ B, from or.inr (h h1))
   (assume h1: ¬A, show ¬ A ∨ B, from or.inl h1)