/-
CS 2120 F22 Homework #4. Due Oct 13.
-/

/- #1A [10 points]

Write a formal proposition stating that
logical and (∧) is associative. That is,
for arbitrary propositions, P, Q, and R,
P ∧ (Q ∧ R) is true *iff* (P ∧ Q) ∧ R is,
too. Replace the placeholder (_) with your
answer.
-/

def and_associative : Prop := ∀ (P Q R), P ∧ Q ∧ R ↔ (P ∧ Q) ∧ R

-- and is right associative

/- #1B [10 points]

Give an English language proof. Identify
the inference rules of predicate logic
that you use in your reasoning.
-/

/-
Answer:
-/

/- #1C [5 points]

Give a formal proof of the proposition.
Hint: unfold and_associative to start.
-/

theorem and_assoc_true : and_associative :=
begin
unfold and_associative,     -- expand definition of and_associative
assume P Q R,               -- ∀ intro
apply iff.intro _ _,        -- iff intro

-- forward
assume pqr,                 -- assume premise
cases pqr with p qr,        -- "unbox" proofs of P and of Q ∧ R
cases qr with q r,          -- "unbox" proofs of Q and R
let pq := (and.intro p q),  -- "rebox" proofs of P and of Q to prove P ∧ Q
apply (and.intro pq r),     -- "box" that up with a proof of r
-- that complete the proof in the forward direction

-- reverse

end

/- #2A [10 points]

Write the proposition that ∨ is associative.,
analogous to the proposition about ∧ in #1.
-/

def or_associative : Prop :=
  _

/- #2B [10 points]

Write an English language proof of it, citing
the specific inference rules you use in your
reasoning.
-/

/- #2C [5 points]

Complete the following formal proof.
-/

theorem or_associative_true : or_associative :=
begin
end

/- #3A [10 points]
Write a formal statement of the proposition.
-/

def arrow_transitive : Prop :=
  _

/-
If there smoke there's fire
If there's fire there's light
If there's light, everything's good
And there's smoke. So everything's good.
Right?
-/

-- The basic propositions
variables (Smoke Fire Light Good : Prop)
-- The implications
variables (sf : Smoke → Fire) (fl : Fire → Light) (lg : Light → Good)
-- The premise
variable (s : Smoke)

example : ∀ (S F L G : Prop), (S → F) → (F → L) → (L → G) → S → G:=
begin
assume S F L G,   -- assume the basic propositions
assume sf fl lg,  -- assume the implication hypotheses
assume s,         -- assume there's smoke, now show everything good

/- this works
apply lg,
apply fl,
apply sf,
exact s
-/

-- so does this
exact lg (fl (sf s)),
-- make sure you understand it both ways
-- understand arrow elimination
-- arrow elimination is like function application!
end

/-
Eercise with negation
-/

example :0 ≠ 1 :=
begin
assume p,
cases p,
end

example : ∀ P, ¬(P ∧ ¬P) :=
begin
assume P,
assume pandnp,
cases pandnp with p np,
apply false.elim (np p),
end

/- #3B [10 points]

Write an English language proof of the proposition
that for any propositions, X, Y, and X, it's the
case that (X → Y) → (Y → Z) → (X → Z). In other
words, implication is "transitive." Hint: Recall
that if you have a proof of, say, X → Y, and you
have a proof of X, you can derive a proof of Y by
arrow elimination. Think of it as applying a proof
of an implication to a proof of its premise to get
yourself a proof of its conclusion.
-/

/- #3C [5 points].
Write a formal proof of it.
-/

/- #4
Suppose that if it's raining then the streets
are wet. This problem requires you to prove that
if the streets are not wet then it's not raining.
-/

/- #4A [10 points]

Start by writing the proposition in predicate
logic by completing the following answer.
-/

def contrapositive : Prop :=
  ∀ (Raining Wet : Prop),
    (Raining → Wet) → (¬Raining → ¬Wet)

/- #4B [10 points].
-/

theorem contrapositive_valid : contrapositive :=
begin

/- #4C [5 points].

Give an English language proof of it.
-/

/- #5. Extra credit.

Complete the following formal proof of the
proposition that if for any proposition P,
P ∨ ¬P is true, then for any propositions,
X and Y, if it's not the case that X or Y
is true then it is the case that ¬X and ¬Y
is true.
-/

theorem demorgan1 :
  (∀ (P : Prop), P ∨ ¬ P) →
    ∀ (X Y : Prop),
      ¬(X ∨ Y) → (¬X ∧ ¬Y) :=
begin
assume em X Y nxory,
cases (em X) with x nx,
let foo := or.intro_left Y x,
_
end

/-
A comment on or.intro_left and or.intro_right.
In Lean each of these takes two arguments: a
proof of the disjunct -- the proposition on
one side of the ∨ -- that is to be proven true,
*and* it takes as an argument the proposition
that is not being proven true. In applications
of these rules the proposition argument (not
being proven) comes first, while the proof
argument comes second.

The reason is that Lean needs to know what
overall proposition is being proved. From the
proof argument it can infer the proposition
being proved, but it needs the other proposition
as well to know the full (X ∨ Y) disjunction to
be proved.

Here's an example:
-/

example : 0 = 0 ∨ 0 = 1 :=
begin
apply or.intro_left (0 = 1) rfl
/-
The "rfl" serves as a proof of 0=0.
But in addition, as the first argument
to or.intro, we need to provide the
*proposition* that is not being proved.
Here's that's (0 = 1). In contexts
where Lean can infer both disuncts,
you can use the simpler or.inl or
or.inr, each of which just takes one
argument: a proof of the left or of
the right side, respectively.
-/
end


/-
Copyright (c) 2019 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin
-/
import data.list.range

open list function nat

namespace list
namespace nat

/-- The antidiagonal of a natural number `n` is the list of pairs `(i,j)` such that `i+j = n`. -/
def antidiagonal (n : ℕ) : list (ℕ × ℕ) :=
(range (n+1)).map (λ i, (i, n - i))

/-- A pair (i,j) is contained in the antidiagonal of `n` if and only if `i+j=n`. -/
@[simp] lemma mem_antidiagonal {n : ℕ} {x : ℕ × ℕ} :
  x ∈ antidiagonal n ↔ x.1 + x.2 = n :=
begin
  rw [antidiagonal, mem_map], split,
  { rintros ⟨i, hi, rfl⟩, rw [mem_range, lt_succ_iff] at hi, exact add_sub_of_le hi },
  { rintro rfl, refine ⟨x.fst, _, _⟩,
    { rw [mem_range, add_assoc, lt_add_iff_pos_right], exact zero_lt_succ _ },
    { exact prod.ext rfl (nat.add_sub_cancel_left _ _) } }
end

/-- The length of the antidiagonal of `n` is `n+1`. -/
@[simp] lemma length_antidiagonal (n : ℕ) : (antidiagonal n).length = n+1 :=
by rw [antidiagonal, length_map, length_range]

/-- The antidiagonal of `0` is the list `[(0,0)]` -/
@[simp] lemma antidiagonal_zero : antidiagonal 0 = [(0, 0)] :=
rfl

/-- The antidiagonal of `n` does not contain duplicate entries. -/
lemma nodup_antidiagonal (n : ℕ) : nodup (antidiagonal n) :=
nodup_map (@left_inverse.injective ℕ (ℕ × ℕ) prod.fst (λ i, (i, n-i)) $ λ i, rfl) (nodup_range _)

@[simp] lemma antidiagonal_succ {n : ℕ} :
  antidiagonal (n + 1) = (0, n + 1) :: ((antidiagonal n).map (prod.map nat.succ id) ) :=
begin
  simp only [antidiagonal, range_succ_eq_map, map_cons, true_and, nat.add_succ_sub_one, add_zero,
    id.def, eq_self_iff_true, nat.sub_zero, map_map, prod.map_mk],
  apply congr (congr rfl _) rfl,
  ext; simp,
end

end nat
end list


def chℕ := Π X : Type, (X → X) → X → X

namespace chnat

open nat

definition to_nat (m : chℕ) : ℕ := m ℕ nat.succ 0

def of_nat : ℕ → chℕ
| (zero) X f x := x
| (succ n) X f x := f (of_nat n X f x) -- f (f^n x)

def pow : chℕ → chℕ → chℕ :=
λ m n, λ X f x, n (X → X) (m X) f x

#print chnat.pow

def pow' (m n : chℕ) : chℕ
| X f x := n (X → X) (m X) f x

#print chnat.pow'

theorem of_nat_pow (m n : ℕ) : of_nat (m ^ n) = pow (of_nat m) (of_nat n) := begin
induction n with n H;funext,refl,
unfold has_pow.pow,
unfold nat.pow,
unfold has_pow.pow at H,

end

end chnat


/- LoVe Exercise 11: Logical Foundations of Mathematics -/

import .love11_logical_foundations_of_mathematics_demo

namespace LoVe

universe variable u

set_option pp.beta true

/- Question 1: Subtypes -/

namespace my_vector

/- Recall the definition of vectors from the lecture: -/

#check vector

/- The following function adds two lists of integers elementwise. If one
function is longer than the other, the tail of the longer function is
truncated. -/

def list_add : list ℤ → list ℤ → list ℤ
| []        []        := []
| (x :: xs) (y :: ys) := (x + y) :: list_add xs ys
| []        (y :: ys) := []
| (x :: xs) []        := []

/- 1.1. Show that if the lists have the same length, the resulting list also has
that length. -/

lemma length_list_add :
  ∀(xs : list ℤ) (ys : list ℤ) (h : list.length xs = list.length ys),
    list.length (list_add xs ys) = list.length xs
| []        []        :=
  by simp [list_add]
| (x :: xs) (y :: ys) :=
  begin
    simp [list_add, length],
    intro h,
    rw length_list_add xs ys h
  end
| []        (y :: ys) :=
  begin
    intro h,
    cases h
  end
| (x :: xs) []        :=
  begin
    intro h,
    cases h
  end

/- 1.2. Define componentwise addition on vectors using `list_add` and
`length_list_add`. -/

def add {n : ℕ} : vector ℤ n → vector ℤ n → vector ℤ n :=
λx y, subtype.mk (list_add (subtype.val x) (subtype.val y))
  begin
    rw length_list_add,
    { exact subtype.property x },
    { rw [subtype.property x, subtype.property y] }
  end

/- 1.3. Show that `list_add` and `add` are commutative. -/

lemma list_add_comm :
  ∀(xs : list ℤ) (ys : list ℤ), list_add xs ys = list_add ys xs
| []        []        := by refl
| (x :: xs) (y :: ys) := by simp [list_add]; rw list_add_comm
| []        (y :: ys) := by refl
| (x :: xs) []        := by refl

lemma add_comm {n : ℕ} (x y : vector ℤ n) :
  add x y = add y x :=
begin
  apply subtype.eq,
  simp [add],
  apply list_add_comm
end

end my_vector

/- Question 2: Integers as Quotients -/

/- Recall the construction of integers from the lecture: -/

#check myℤ.rel
#check rel_iff
#check myℤ

/- 2.1. Define negation using `quotient.lift`. -/

def neg : myℤ → myℤ :=
quotient.lift (λpn, ⟦(prod.snd pn, prod.fst pn)⟧)
  begin
    intros a b h,
    cases a,
    cases b,
    apply quotient.sound,
    simp [rel_iff] at h ⊢,
    linarith
  end

/- 2.2. Prove the following lemmas. -/

lemma neg_mk (p n : ℕ) :
  neg ⟦(p, n)⟧ = ⟦(n, p)⟧ :=
by refl

lemma myℤ.neg_neg (a : myℤ) :
  neg (neg a) = a :=
begin
  apply quotient.induction_on a,
  intro a,
  cases a,
  simp [neg_mk]
end

/- Question 3: Nonempty Types -/

/- In the lecture, we saw the inductive predicate `nonempty` that states that a
type has at least one element: -/

#print nonempty

/- 3.1. The purpose of this exercise is to think about what would happen if all
types had at least one element. To investigate this, we introduce this fact as
an axiom as follows. Introducing axioms should be generally avoided or done
with great care, since they can easily lead to contradictions, as we will
see. -/

axiom sort_nonempty (α : Sort u) :
  nonempty α

/- This axiom gives us a fact `sort_nonempty` without having to prove it. It
resembles a lemma proved by sorry, just without the warning. -/

#check sort_nonempty

/- Prove that this axiom leads to a contradiction, i.e., lets us derive
`false`. -/

lemma proof_of_false :
  false :=
by exact classical.choice (sort_nonempty false)

-- alternative proof:
lemma proof_of_false' :
  false :=
begin
  cases sort_nonempty false with h,
  exact h
end

/- 3.2 (**optional**). Prove that even the following weaker axiom leads to a
contradiction. Of course, you may not use the axiom or the lemma from 3.1.

Hint: Subtypes can help. -/

axiom all_nonempty_Type (α : Type u) :
  nonempty α

lemma proof_of_false₂ : false :=
begin
  let t : Type := {a : ℕ // false},
  have h : nonempty t := sort_nonempty t,
  let x : t := classical.choice h,
  exact subtype.property x
end

-- alternative proof:
lemma proof_of_false₂' : false :=
begin
  let t : Type := {a : ℕ // false},
  cases all_nonempty_Type t with x,
  exact subtype.property x
end

/- Question 4 (**optional**): Hilbert Choice -/

/- The following command enables noncomputable decidability on every `Prop`. The
`priority 0` attribute ensures this is used only when necessary; otherwise, it
would make some computable definitions noncomputable for Lean. -/

local attribute [instance, priority 0] classical.prop_decidable

/- 4.1 (**optional**). Prove the following lemma. -/

lemma exists_minimal_arg.aux (f : ℕ → ℕ) :
  ∀x n, f n = x → ∃n, ∀i, f n ≤ f i
| x n eq :=
  begin
    -- this works thanks to `classical.prop_decidable`
    by_cases (∃n', f n' < x),
    { cases h with n' h,
      exact exists_minimal_arg.aux _ n' rfl },
    { have h' : ∀n', x ≤ f n',
      { intro n',
        apply le_of_not_gt _,
        intro h',
        apply h,
        use n',
        exact h' },
      apply exists.intro n,
      rw eq,
      exact h' }
  end

/- Now this interesting lemma falls off: -/

lemma exists_minimal_arg (f : ℕ → ℕ) :
  ∃n : ℕ, ∀i : ℕ, f n ≤ f i :=
exists_minimal_arg.aux f _ 0 rfl

/- 4.2 (**optional**). Use what you learned in the lecture notes to define the
following function, which returns the (or an) index of the minimal element in
`f`'s image. -/

noncomputable def minimal_arg (f : ℕ → ℕ) : ℕ :=
classical.some (exists_minimal_arg f)

/- 4.3 (**optional**). Prove the following characteristic lemma about your
definition. -/

lemma minimal_arg_spec (f : ℕ → ℕ) :
  ∀i : ℕ, f (minimal_arg f) ≤ f i :=
classical.some_spec (exists_minimal_arg f)

end LoVe


import data.nat.gcd
import data.real.irrational
import tactic

#print nat.coprime

example (m n : nat) (h : m.coprime n) : m.gcd n = 1 := h

example : nat.coprime 12 7 := by norm_num
example : nat.gcd 12 8 = 4 := by norm_num

#check @nat.prime_def_lt

example (p : ℕ) (prime_p : nat.prime p) : 2 ≤ p ∧ ∀ (m : ℕ), m < p → m ∣ p → m = 1 :=
by rwa nat.prime_def_lt at prime_p

#check @nat.prime.eq_one_or_self_of_dvd

example (p : ℕ) (prime_p : nat.prime p) : ∀ (m : ℕ), m ∣ p → m = 1 ∨ m = p :=
prime_p.eq_one_or_self_of_dvd

example : nat.prime 17 := by norm_num

-- commonly used
example : nat.prime 2 := nat.prime_two
example : nat.prime 3 := nat.prime_three

#check @nat.prime.dvd_mul
#check @nat.prime.dvd_mul
#check nat.prime_two.dvd_mul

lemma even_of_even_sqr {m : ℕ} (h : 2 ∣ m^2) : 2 ∣ m :=
begin
  rw [pow_two, nat.prime_two.dvd_mul] at h,
  cases h; assumption,
end

example {m n : ℕ} (coprime_mn : m.coprime n) : m^2 ≠ 2 * n^2 :=
begin
  intro sqreq,
  have : 2 ∣ m,
    apply even_of_even_sqr,
    use [n^2, sqreq],

  obtain ⟨k, meq⟩ := dvd_iff_exists_eq_mul_left.mp this,

  have : 2 * (2 * k^2) = 2 * n^2,
    rw [←sqreq, meq], ring,

  have : 2 * k^2 = n^2,
    apply (mul_right_inj' _).mp,
    from this, norm_num,

  have : 2 ∣ n,
    apply even_of_even_sqr,
    use [k^2, this.symm],

  have : 2 ∣ m.gcd n,
    apply (dvd_gcd_iff 2 m n).mpr,
    split, assumption',

  have : 2 ∣ 1,
    convert this,
    from (nat.coprime_iff_gcd_eq_one.mp coprime_mn).symm,

  norm_num at this,
end

lemma dvd_of_dvd_sq {p m : ℕ} (pp : p.prime) (h : p ∣ m^2) : p ∣ m :=
begin
  rw [pow_two, nat.prime.dvd_mul pp] at h,
  cases h, assumption',
end

example {m n p : ℕ} (coprime_mn : m.coprime n) (prime_p : p.prime) : m^2 ≠ p * n^2 :=
begin
  intro heq,
  have : p ∣ m,
    apply dvd_of_dvd_sq prime_p,
    use [n^2, heq],

  obtain ⟨k, meq⟩ := dvd_iff_exists_eq_mul_left.mp this,

  have : p * (p * k^2) = p * n^2,
    rw [←heq, meq], ring,

  have : p * k^2 = n^2,
    apply (mul_right_inj' _).mp,
    from this, exact nat.prime.ne_zero prime_p,

  have : p ∣ n,
    apply dvd_of_dvd_sq prime_p,
    use [k^2, this.symm],

  have : p ∣ m.gcd n,
    apply (dvd_gcd_iff p m n).mpr,
    split, assumption',

  have : p ∣ 1,
    convert this,
    rw nat.coprime_iff_gcd_eq_one.mp,
    assumption,

  from nat.prime.not_dvd_one prime_p this,
end

#check nat.factors
#check nat.prime_of_mem_factors
#check nat.prod_factors
#check nat.factors_unique

#check @nat.factors_prime
#eval (12 : ℕ).factors.count 2 -- 2

theorem nat.count_factors_mul_of_pos (m n p : ℕ) (mpos : 0 ≠ m) (npos : 0 ≠ n) :
  (m * n).factors.count p = m.factors.count p + n.factors.count p :=
begin
  dsimp,
  repeat {rw nat.factors_count_eq,},
  rw nat.factorization_mul,
    refl,
    from mpos.symm,
    from npos.symm,
end

theorem nat.factors_count_pow (n k p : ℕ) : (n^k).factors.count p = k * n.factors.count p :=
begin
  repeat {rw nat.factors_count_eq,},
  rw nat.factorization_pow,
  refl,
end

theorem nat.prime_one_factor (p : ℕ) (prime_p : p.prime) : p.factors.count p = 1 :=
begin
  rw nat.factors_prime prime_p,
  simp,
end

example {m n p : ℕ} (nnz : n ≠ 0) (prime_p : p.prime) : m^2 ≠ p * n^2 :=
begin
  intro heq,
  have nsq_nez : n^2 ≠ 0,
    by simpa,

  have eq1 : (m^2).factors.count p = 2 * m.factors.count p,
    apply nat.factors_count_pow,

  have eq2 : (p * n^2).factors.count p = 2 * n.factors.count p + 1,
    rw nat.count_factors_mul_of_pos p (n^2),
    rw nat.prime_one_factor p prime_p,
    rw nat.factors_count_pow, linarith,
    from ne_of_lt (nat.prime.pos prime_p),
    from nsq_nez.symm,

  have : (2 * m.factors.count p) % 2 = (2 * n.factors.count p + 1) % 2,
    rw [←eq1, heq, eq2],

  rw [add_comm, nat.add_mul_mod_self_left, nat.mul_mod_right] at this,
  norm_num at this,
end

example {m n k r : ℕ} (nnz : n ≠ 0) (pow_eq : m^k = r * n^k) :
  ∀ p : ℕ, p.prime → k ∣ r.factors.count p :=
begin
  intros p prime_p,
  cases r with r,
  { simp,},

  have npow_nz : n^k ≠ 0,
    intro npowz, from nnz (pow_eq_zero npowz),

  have eq1 : (m^k).factors.count p = k * m.factors.count p,
    apply nat.factors_count_pow,

  have eq2 : (r.succ * n^k).factors.count p =
      k * n.factors.count p + r.succ.factors.count p,
    rw [nat.count_factors_mul_of_pos, nat.factors_count_pow, add_comm],
    show 0 ≠ r.succ, intro, contradiction,
    show 0 ≠ n^k, from npow_nz.symm,

  have : r.succ.factors.count p = k * m.factors.count p - k * n.factors.count p,
    rw [←eq1, pow_eq, eq2], simp,

  rw this,
  apply nat.dvd_sub',
  repeat {apply dvd_mul_right,},
end


/-
Copyright (c) 2023 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics
import group_theory.quotient_group -- import Lean's quotient groups

/-

# Quotient groups

mathlib has quotient groups. Here's how they work.

-/

-- let G be a group and let N be a normal subgroup
variables (G : Type) [group G] (N : subgroup G) [subgroup.normal N]

-- The underlying type (or set) of the quotient group. Note that `⧸` is `\quot`, not the slash
-- character `/` in your keyboard.

example : Type := G ⧸ N

-- Let's check that the typeclass inference system can find the group structure on the quotient
example : group (G ⧸ N) := infer_instance

-- The group homomorphism from `G` to `G ⧸ N`

example : G →* G ⧸ N := quotient_group.mk' N

-- Remarks:
-- (1) Why `quotient_group.mk'` and not `quotient_group.mk`? Because the version without the `'`
-- is just the function, the version with the `'` is the group homomorphism.
-- (2) Why does `quotient_group.mk' N` want to have `N` as an input but not `G`? It's because
-- the type of `N` is `subgroup G` so Lean can figure out `G` from `N`: if you like, `N` "knows
-- which group it's a subgroup of".

-- Here is the basic API you need for quotient groups.

-- the map G → G ⧸ N is surjective
example : function.surjective (quotient_group.mk' N) := quotient_group.mk'_surjective N

-- Two elements of G have the same image in `G ⧸ N` iff they differ by an element of `N`
example (x y : G) : quotient_group.mk' N x = quotient_group.mk' N y ↔ ∃ n ∈ N, x * n = y :=
quotient_group.mk'_eq_mk' N

-- There is of course much more API, but if you want to get some practice you can
-- just develop some of it yourself from these two functions.

example : (quotient_group.mk' N).ker = N :=
begin
  ext a,
  rw monoid_hom.mem_ker,
  have h : quotient_group.mk' N 1 = 1 := monoid_hom.map_one _,
  rw [← h, eq_comm, quotient_group.mk'_eq_mk'],
  -- now it's just `one_mul` and logic
  simp,
end

-- The "universal property" of quotients says that if you have a group homomorphism `φ : G →* H`
-- whose kernel contains `N` then it "extends" to a group homomorphism `ψ : G ⧸ N →* H`
-- such that the composite map `ψ ∘ (quotient_group.mk' N)` equals `φ`. Given `φ`, the `ψ` with
-- this property is called `quotient_group.lift N φ h`, where `h` is a proof of `∀ x, x ∈ N → φ x = 1`.

variables (H : Type) [group H] (φ : G →* H) (h : ∀ x, x ∈ N → φ x = 1)

example : G ⧸ N →* H := quotient_group.lift N φ h

-- The proof that if `x : G` then `(quotient_group.lift N φ h) ((quotient_group.mk' N) x) = φ x`
-- is, amazingly, `refl`.

example (x : G) : (quotient_group.lift N φ h) ((quotient_group.mk' N) x) = φ x :=
begin
  refl,
end

-- Technical remark: this would not be the case if quotient groups were *defined* to
-- be cosets. In Lean quotient groups are an *opaque definition*. What do I mean by this?
-- You probably learnt in algebra that if G is a group and H is a normal subgroup then the
-- quotient G⧸H has elements which are *equal* to cosets of H. In Lean this is not true.
-- A term of the quotient type G⧸H cannot be taken apart with `cases` because it is not *equal* to
-- a coset. But the universal property `quotient_group.lift` is all we need; we don't
-- need to worry about the underlying definition of the quotient.

-- Example. Let's use `quotient_group.lift` to define the following map. Say `φ : G →* H` is a
-- group hom and we have normal subgroups `N : subgroup G` and `P : subgroup H` such that `φ N ≤ P`.
-- Then the induced map `G →* H ⧸ P` has `N` in the kernel, so it "lifts" to a group hom
-- `ρ : G ⧸ N →* H ⧸ P` with the property that for all `x : G`,
-- `ρ (quotient_group.mk' N x) = quotient_group.mk' P (φ x)`. Let's define `ρ` and prove
-- this equality.

variables {G H φ N} {P : subgroup H}
variable [P.normal]

def ρ (h : N.map φ ≤ P) : G ⧸ N →* H ⧸ P :=
quotient_group.lift N ((quotient_group.mk' P).comp φ)
begin
  -- we are using `quotient_group.lift` so we need to supply the proof that `(mk' P).comp φ` kills `N`
  intros g hg,
  -- the simplifier can help out with this mess:
  suffices : φ g ∈ P,
  { simpa, },
  apply h,
  use g,
  exact ⟨hg, rfl⟩,
end

-- Now let's prove that `ρ ∘ mk' N = mk' P ∘ φ`

/-
    G ----φ----> H
    |            |
    |            |
   mk'           mk'
    |            |
    \/           \/
  G ⧸ N --ρ--> H ⧸ P

-/
open quotient_group -- no idea why I didn't do this earlier

example (h : N.map φ ≤ P) (x : G) : ρ h (mk' N x) = mk' P (φ x) :=
begin
  -- this proof does my head in
  refl,
end


/-
Proofs:
- very structured, logical

Propositions can formalized as types and proofs are values of those types.
To Prove a Proposition : We assert there is a value at play.
-/
-- review: we define day to be a type. [Computation type when capital t in type : Type]
inductive day : Type
| mon
|tue
|wed
|thu
|fri
|sat
|sun

/-
Now we can define variables of this type.
-/
open day
def l: day := _ -- hole : placeholder, := - turnstyle
def d: day :=
begin
    --sccripting terms
    exact tue,  -- exact must give value which is required by goal
end

/-
emily_from_cville
-/

inductive emily_from_cville: Prop -- logical type, not computational
/-
Constructors define the values, which we now accept as proofs of the proposition. Proofs in lean are values of *logical* types.
-/
| drivers_license
| passport
| utility_bill

inductive empty_data_type : Type
inductive myfalse : Prop

open emily_from_cville

def a_proof : emily_from_cville := _
-- waiting for a constructor

def a_proof': emily_from_cville :=
begin
    exact utility_bill,
end

theorem a_proof'' : emily_from_cville :=
begin
    exact passport
end

inductive person :Type
|mari
|jose
|jane
|bill

open person
-- proposition with a parameter is a predicate

-- is_from_cville is a predicate
-- a predicate can be thought of defining a property of the parameter to which it applies.

inductive is_from_cville : person → Prop
|proof_for:
    ∀ (p: person)
        p = mari → is_from_cville mari

open is_from_cville
#check is_from_cville mari
#check is_from_cville jose
#check is_from_cville jane
#check is_from_cville bill

#check proof_for mari  -- proof for is a constructor, all that is required is that mari == mari

theorem mari_is_from_cville : is_from_cville mari :=
begin
    --scripting command
    apply proof_for _  _,
    --proof for is a constructor, given any person p that if you can prove p = mari, then you have a proof that mari is from cville.
    exact mari,
    exact (eq.refl mari),
end

theorem jose_is_from_cville : is_from_cville jose :=
begin
    --scripting command
    apply proof_for _  _,
    --proof for is a constructor, given any person p that if you can prove p = mari, then you have a proof that mari is from cville.
    exact jose,
    exact (eq.refl jose),
end
 -- equality is just a predicate
 /-
 eq     a           b

 is the same as

 a = b

 eq     1       3
 1 = 3 - a false proposition, but a proposition nevertheless

 eq 1   1
 1 = 1

 eq is a proof constructor where there is an inductive def of eq which is nat → nat → prop where | refl ℕ → eq n n

 equality as a binary relation is reflexive

 relation : a set of pairs

 Symetry : if a,b is in, then so is b,a

 equality is symetric and reflexive

 Property of relations:

 Binary relation is transitive if a likes b, b likes c then a likes c.

 -/

 def evenb (n : nat) : bool :=
    n %2 = 0 -- computational predicate yields a bool. Logical predicate yields a proposition.

inductive is_zero : ℕ → Prop
| zmk : ∀ (n:ℕ ), n = 0 → is_zero n

open is_zero


/-Please allow me to make some statements. This is problem sheet 1
of an introductory course, none of which belongs to me.-/

/-
Copyright (c) 2021 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics

/-!

# Logic in Lean, example sheet 1 : "implies" (`→`)

We learn about propositions, and implications `P → Q` between them. You can get
this arrow by typing `\to` or `\r`. Mathematicians usually write the
implication arrow as `P ⇒ Q` but Lean prefers a single arrow.

## The absolute basics

`P : Prop` means that `P` is a true-false statement. `h : P` means
that `h` is a proof that `P` is true, or you can regard `h` as an
assumption that `P` is true; logically these are the same. Stuff above
the `⊢` symbol is your assumptions. The statement to the right of it is
the goal. Your job is to prove the goal from the assumptions.

## Tactics you will need

To solve the levels on this sheet you will need to know how to use the
following tactics:

* `intro`
* `exact`
* `apply`

### The `intro` tactic

Mathematical background: `intro h,` says "To prove `P → Q`, you can assume
that `P` is true (call this assumption `h`) and then it
suffices to prove `Q`."

Lean: If your goal is `⊢ P → Q` then `intro h,` will introduce a
hypothesis `h : P` (i.e. a hypothesis that `P` is true)
and change the goal to `⊢ Q`.

### The `exact` tactic

Mathematics: If you have an assumption `h` that `P` is true,
and your goal is to prove that `P` is true, then `exact h,`
will solve this goal.

Lean: If your goal is `⊢ P` and you have a hypothesis `h : P`
then `exact h,` will solve it.

### The `apply` tactic

Mathematics: `apply` is *arguing backwards*. It is like "it suffices to...".
If you're trying to prove `Q`, and you know `h : P → Q` is true, then it
suffices to prove `P`. So `apply h,` *changes the goal* from `Q` to `P`. The key
point to remember is that `apply h` will only work when `h` is an implication,
and it will only work when the *conclusion* of `h` *matches the goal*.

Lean: If your goal is `⊢ Q` and you have `h : P → Q` then `apply h,` will
change the goal to `⊢ P`.

-/

/-

## Worked examples

Click around in the proofs to see the tactic state (on the right) change.
The tactic is implemented and the state changes just before the comma.
I will use the following conventions: variables with capital
letters like `P`, `Q`, `R` denote propositions
(i.e. true/false statements) and variables whose names begin
with `h` like `h1` or `hP` are proofs or hypotheses.

-/

-- Throughout this sheet, `P`, `Q` and `R` will denote propositions.
variables (P Q R : Prop)

-- Here are some examples of `intro`, `exact` and `apply` being used.

-- Assume that `P` and `Q` and `R` are all true. Deduce that `P` is true.
example (hP : P) (hQ : Q) (hR : R) : P :=
begin
  -- note that `exact P` does *not* work. `P` is the proposition, `hP` is the proof.
  exact hP,
end

-- Assume `Q` is true. Prove that `P → Q`.
example (hQ : Q) : P → Q :=
begin
  -- The goal is of the form `X → Y` so we can use `intro`
  intro h, -- now `h` is the hypothesis that `P` is true.
  -- Our goal is now the same as a hypothesis so we can use `exact`
  exact hQ,
end

-- Assume `P → Q` and `P` is true. Deduce `Q`.
example (h : P → Q) (hP : P) : Q :=
begin
  -- our goal is `⊢ Q` which matches with the conclusion of `h` so `apply` works
  apply h,
  -- now our goal has changed to `P` which is an assumption
  exact hP,
end

/-

## Examples for you to try

Delete the `sorry`s and replace them with comma-separated tactic proofs
using `intro`, `exact` and `apply`.

-/

/-- Every proposition implies itself. -/
example : P → P :=
begin
  intro hP,
  exact hP,
end

/-

Note that `→` is not associative: in general `P → (Q → R)` and `(P → Q) → R`
might not be equivalent. This is like subtraction on numbers -- in general
`a - (b - c)` and `(a - b) - c` might not be equal.

So if we write `P → Q → R` then we'd better know what this means.
The convention in Lean is that it means `P → (Q → R)`. If you think
about it, this means that to deduce `R` you will need to prove both `P`
and `Q`. In general to prove `P1 → P2 → P3 → ... Pn` you can assume
`P1`, `P2`,...,`P(n-1)` and then you have to prove `Pn`.

So the next level is asking you prove that `P → (Q → P)`.

-/
example : P → Q → P :=
begin
  intros hP hQ,
  exact hP
end

/-- If we know `P`, and we also know `P → Q`, we can deduce `Q`.
This is called "Modus Ponens" by logicians. -/
example : P → (P → Q) → Q :=
begin
  intros hP hPQ,
  apply hPQ,
  exact hP
end

/-- `→` is transitive. That is, if `P → Q` and `Q → R` are true, then
  so is `P → R`. -/
example : (P → Q) → (Q → R) → (P → R) :=
begin
  intros hPQ hQR hP,
  apply hQR,
  apply hPQ hP,
end

-- If `h : P → Q → R` with goal `⊢ R` and you `apply h`, you'll get
-- two goals! Note that tactics operate on only the first goal.
example : (P → Q → R) → (P → Q) → (P → R) :=
begin
  intros hPQR hPQ hP,
  apply hPQR,
  exact hP,
  apply hPQ hP,
end

-- Now they get a little harder. You can skip these if
-- you feel like you know what you're doing.

variables (S T : Prop)

example : (P → R) → (S → Q) → (R → T) → (Q → R) → S → T :=
begin
  intros hPR hSQ hRT hQR hS,
  exact hRT (hQR (hSQ hS)),
end

example : (P → Q) → ((P → Q) → P) → Q :=
begin
  intros hPQ hPQP,
  have hP: P, by exact hPQP hPQ,
  exact hPQ hP,
end

example : ((P → Q) → R) → ((Q → R) → P) → ((R → P) → Q) → P :=
begin
  intros hPQR hQRP hRPQ,
  apply hQRP,
  intro hQ,
  have hPQ : P → Q, {intro hP, exact hQ,},
  exact hPQR hPQ,
end

example : ((Q → P) → P) → (Q → R) → (R → P) → P :=
begin
  intros hQPP hQR hRP,
  apply hQPP,
  intro hQ,
  exact hRP (hQR hQ),
end

example : (((P → Q) → Q) → Q) → (P → Q) :=
begin
  intros hPQQQ hP,
  apply hPQQQ,
  intro hPQ,
  exact hPQ hP,
end

example :
  (((P → Q → Q) → ((P → Q) → Q)) → R) →
  ((((P → P) → Q) → (P → P → Q)) → R) →
  (((P → P → Q) → ((P → P) → Q)) → R) → R :=
begin
  intros h₁ h₂ h₃,
  apply h₂,
  intros hPPQ hPQ,
  have hPP: P → P, {intro hP, exact hP,},
  have hQ: Q, exact hPPQ hPP,
  intro hP,
  exact hQ,
end


import tactic

open_locale classical -- def of max a b is "if a <= b then b else a"
noncomputable theory -- same reason

-- A linear order is a reflexive, transitive, antisymmetric and total relation `≤`

variables {X : Type} [linear_order X] {a b c : X}

-- Note in the below that sometimes we have to supply the variable names and other times not.
-- I am scared after my NNG experience to break with tradition and start
-- changing mathlib conventions. They are there for a reason (you don't need
-- them when there are implications involved, because you can guess the
-- variables from the hypotheses)

-- I think that the user would be happy to accept all of these as axioms,
-- even though strictly speaking one could define a < b to be a ≤ b ∧ a ≠ b
-- and then prove the last two.

example : a ≤ a := le_refl a

example : a ≤ b → b ≤ c → a ≤ c := le_trans

example : a ≤ b → b ≤ a → a = b := le_antisymm

-- I think this is more useful in practice than trichotomy
example : a ≤ b ∨ b ≤ a := le_total a b

example : a < b ↔ a ≤ b ∧ a ≠ b := lt_iff_le_and_ne

example : a ≤ b → b < c → a < c := lt_of_le_of_lt

example : a < b → b ≤ c → a < c := lt_of_lt_of_le

-- max is already defined so we work in a namespace

namespace test

-- I think this definition might be hard to work with
def max (a b : X) := if a ≤ b then b else a

-- need if_pos to do this one
theorem max_eq_right (hab : a ≤ b) : max a b = b :=
begin
  unfold max,
  rw if_pos hab,
end

-- need if_neg to do this one
theorem max_eq_left (hba : b ≤ a) : max a b = a :=
begin
  by_cases hab : a ≤ b,
  { rw max_eq_right hab,
    exact le_antisymm hba hab,
  },
  { unfold max,
    rw if_neg hab,
  }
end

-- but now things work really nicely. Proposal: tell them that
-- max_eq_left and max_eq_right are axioms of max, and don't
-- tell them the definition. Note that max_eq_left and max_eq_right
-- together are enough to deduce that max a b is what it is,
-- because of le_antisymm

-- level 1
theorem max_choice (a b : X) : max a b = a ∨ max a b = b :=
begin
  cases le_total a b with hab hba,
  { right,
    exact max_eq_right hab
  },
  { left,
    exact max_eq_left hba
  }
end

-- level 2
theorem max_comm (a b : X) : max a b = max b a :=
begin
  cases le_total a b with hab hba,
  { rw max_eq_right hab,
    rw max_eq_left hab,
  },
  { rw max_eq_left hba,
    rw max_eq_right hba
  }
end

-- level 3
theorem le_max_left (a b : X) : a ≤ max a b :=
begin
  cases le_total a b with hab hba,
  { rw max_eq_right hab,
    assumption
  },
  { rw max_eq_left hba,
    -- Lean closes a ≤ a automatically because ≤ is reflexive
  }
end

-- level 4
theorem le_max_right (a b : X) : b ≤ max a b :=
begin
  rw max_comm,
  apply le_max_left
end

-- this comes out nicely
-- level 5
theorem max_le (hac : a ≤ c) (hbc : b ≤ c) : max a b ≤ c :=
begin
  cases max_choice a b with ha hb,
  { rw ha,
    assumption
  },
  { rw hb,
    assumption
  }
end

-- and this, if we can teach `apply le_trans _ habc`
-- level 6
theorem max_le_iff : a ≤ c ∧ b ≤ c ↔ max a b ≤ c :=
begin
  split,
  { intro h,
    cases h with hac hbc,
    exact max_le hac hbc
  },
  { intro habc,
    split,
    { apply le_trans _ habc,
      apply le_max_left},
    { apply le_trans _ habc,
      apply le_max_right
    }
  },
end

-- so does this
-- level 7
theorem max_lt (hac : a < c) (hbc : b < c) : max a b < c :=
begin
  cases max_choice a b with ha hb,
  { rw ha,
    assumption
  },
  { rw hb,
    assumption
  }
end

-- level 8
theorem max_lt_iff : a < c ∧ b < c ↔ max a b < c :=
begin
  split,
  { intro h,
    cases h with hac hbc,
    exact max_lt hac hbc
  },
  { intro habc,
    split,
    { apply lt_of_le_of_lt _ habc,
      apply le_max_left},
    { apply lt_of_le_of_lt _ habc,
      apply le_max_right
    }
  },
end

-- level 9
theorem le_max_iff : a ≤ max b c ↔ a ≤ b ∨ a ≤ c :=
begin
  split,
  { intro ha,
    cases le_total b c with hbc hcb,
    { rw max_eq_right hbc at ha,
      right,
      assumption,
    },
    { rw max_eq_left hcb at ha,
      left,
      assumption
    }
  },
  { intro habc,
    cases habc with hab hac,
    { apply le_trans hab,
      apply le_max_left},
    { apply le_trans hac,
      apply le_max_right},
  }
end

-- same as previous one
theorem lt_max_iff : a < max b c ↔ a < b ∨ a < c :=
begin
  split,
  { intro ha,
    cases le_total b c with hbc hcb,
    { rw max_eq_right hbc at ha,
      right,
      assumption,
    },
    { rw max_eq_left hcb at ha,
      left,
      assumption
    }
  },
  { intro habc,
    cases habc with hab hac,
    { apply lt_of_lt_of_le hab,
      apply le_max_left},
    { apply lt_of_lt_of_le hac,
      apply le_max_right},
  }
end

-- I think that's a good API for max. Let's test this hypothesis
-- by seeing how easy it is to make a good API for abs.

end test


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

! This file was ported from Lean 3 source module data.bool.count
! leanprover-community/mathlib commit 8631e2d5ea77f6c13054d9151d82b83069680cb1
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Nat.Parity
import Mathlib.Data.List.Chain

/-!
# List of booleans

In this file we prove lemmas about the number of `false`s and `true`s in a list of booleans. First
we prove that the number of `false`s plus the number of `true` equals the length of the list. Then
we prove that in a list with alternating `true`s and `false`s, the number of `true`s differs from
the number of `false`s by at most one. We provide several versions of these statements.
-/

namespace List

@[simp]
theorem count_not_add_count (l : List Bool) (b : Bool) : count (!b) l + count b l = length l := by
  -- Porting note: Proof re-written
  -- Old proof: simp only [length_eq_countp_add_countp (Eq (!b)), Bool.not_not_eq, count]
  simp only [length_eq_countp_add_countp (· == !b), count, add_right_inj]
  suffices : (fun x => x == b) = (fun a => decide ¬(a == !b) = true); rw [this]
  ext x; cases x <;> cases b <;> rfl
#align list.count_bnot_add_count List.count_not_add_count

@[simp]
theorem count_add_count_not (l : List Bool) (b : Bool) : count b l + count (!b) l = length l := by
  rw [add_comm, count_not_add_count]
#align list.count_add_count_bnot List.count_add_count_not

@[simp]
theorem count_false_add_count_true (l : List Bool) : count false l + count true l = length l :=
  count_not_add_count l true
#align list.count_ff_add_count_tt List.count_false_add_count_true

@[simp]
theorem count_true_add_count_false (l : List Bool) : count true l + count false l = length l :=
  count_not_add_count l false
#align list.count_tt_add_count_ff List.count_true_add_count_false

theorem Chain.count_not :
    ∀ {b : Bool} {l : List Bool}, Chain (· ≠ ·) b l → count (!b) l = count b l + length l % 2
  | b, [], _h => rfl
  | b, x :: l, h => by
    obtain rfl : b = !x := Bool.eq_not_iff.2 (rel_of_chain_cons h)
    rw [Bool.not_not, count_cons_self, count_cons_of_ne x.not_ne_self,
      Chain.count_not (chain_of_chain_cons h), length, add_assoc, Nat.mod_two_add_succ_mod_two]
#align list.chain.count_bnot List.Chain.count_not

namespace Chain'

variable {l : List Bool}

theorem count_not_eq_count (hl : Chain' (· ≠ ·) l) (h2 : Even (length l)) (b : Bool) :
    count (!b) l = count b l := by
  cases' l with x l
  · rfl
  rw [length_cons, Nat.even_add_one, Nat.not_even_iff] at h2
  suffices count (!x) (x :: l) = count x (x :: l) by
    -- Porting note: old proof is
    -- cases b <;> cases x <;> try exact this;
    cases b <;> cases x <;>
    revert this <;> simp only [Bool.not_false, Bool.not_true] <;> intro this <;>
    (try exact this) <;> exact this.symm
  rw [count_cons_of_ne x.not_ne_self, hl.count_not, h2, count_cons_self]
#align list.chain'.count_bnot_eq_count List.Chain'.count_not_eq_count

theorem count_false_eq_count_true (hl : Chain' (· ≠ ·) l) (h2 : Even (length l)) :
    count false l = count true l :=
  hl.count_not_eq_count h2 true
#align list.chain'.count_ff_eq_count_tt List.Chain'.count_false_eq_count_true

theorem count_not_le_count_add_one (hl : Chain' (· ≠ ·) l) (b : Bool) :
    count (!b) l ≤ count b l + 1 := by
  cases' l with x l
  · exact zero_le _
  obtain rfl | rfl : b = x ∨ b = !x := by simp only [Bool.eq_not_iff, em]
  · rw [count_cons_of_ne b.not_ne_self, count_cons_self, hl.count_not, add_assoc]
    exact add_le_add_left (Nat.mod_lt _ two_pos).le _
  · rw [Bool.not_not, count_cons_self, count_cons_of_ne x.not_ne_self, hl.count_not]
    exact add_le_add_right (le_add_right le_rfl) _
#align list.chain'.count_bnot_le_count_add_one List.Chain'.count_not_le_count_add_one

theorem count_false_le_count_true_add_one (hl : Chain' (· ≠ ·) l) :
    count false l ≤ count true l + 1 :=
  hl.count_not_le_count_add_one true
#align list.chain'.count_ff_le_count_tt_add_one List.Chain'.count_false_le_count_true_add_one

theorem count_true_le_count_false_add_one (hl : Chain' (· ≠ ·) l) :
    count true l ≤ count false l + 1 :=
  hl.count_not_le_count_add_one false
#align list.chain'.count_tt_le_count_ff_add_one List.Chain'.count_true_le_count_false_add_one

theorem two_mul_count_bool_of_even (hl : Chain' (· ≠ ·) l) (h2 : Even (length l)) (b : Bool) :
    2 * count b l = length l := by
  rw [← count_not_add_count l b, hl.count_not_eq_count h2, two_mul]
#align list.chain'.two_mul_count_bool_of_even List.Chain'.two_mul_count_bool_of_even

theorem two_mul_count_bool_eq_ite (hl : Chain' (· ≠ ·) l) (b : Bool) :
    2 * count b l =
      if Even (length l) then length l else
      if Option.some b == l.head? then length l + 1 else length l - 1 := by
  by_cases h2 : Even (length l)
  · rw [if_pos h2, hl.two_mul_count_bool_of_even h2]
  · cases' l with x l
    · exact (h2 even_zero).elim
    simp only [if_neg h2, count_cons', mul_add, head?, Option.mem_some_iff, @eq_comm _ x]
    rw [length_cons, Nat.even_add_one, not_not] at h2
    replace hl : l.Chain' (· ≠ ·) := hl.tail
    rw [hl.two_mul_count_bool_of_even h2]
    cases b <;> cases x <;> split_ifs <;> simp <;> contradiction
#align list.chain'.two_mul_count_bool_eq_ite List.Chain'.two_mul_count_bool_eq_ite

theorem length_sub_one_le_two_mul_count_bool (hl : Chain' (· ≠ ·) l) (b : Bool) :
    length l - 1 ≤ 2 * count b l := by
  rw [hl.two_mul_count_bool_eq_ite]
  split_ifs <;> simp [le_tsub_add, Nat.le_succ_of_le]
#align list.chain'.length_sub_one_le_two_mul_count_bool List.Chain'.length_sub_one_le_two_mul_count_bool

theorem length_div_two_le_count_bool (hl : Chain' (· ≠ ·) l) (b : Bool) :
    length l / 2 ≤ count b l := by
  rw [Nat.div_le_iff_le_mul_add_pred two_pos, ← tsub_le_iff_right]
  exact length_sub_one_le_two_mul_count_bool hl b
#align list.chain'.length_div_two_le_count_bool List.Chain'.length_div_two_le_count_bool

theorem two_mul_count_bool_le_length_add_one (hl : Chain' (· ≠ ·) l) (b : Bool) :
    2 * count b l ≤ length l + 1 := by
  rw [hl.two_mul_count_bool_eq_ite]
  split_ifs <;> simp [Nat.le_succ_of_le]
#align list.chain'.two_mul_count_bool_le_length_add_one List.Chain'.two_mul_count_bool_le_length_add_one

end Chain'

end List


import tactic.linarith
import .src_05_if_and_only_if

variables p q r : Prop

namespace mth1001

section rewriting
/-
Statements of the form `p ↔ q` play a cruicial role in logic. Given `h : p ↔ q`, we can
replace `p`, wherever it appears, with `q`. This is called rewriting.
-/

/-
Here, the `rewrite` tactic (abbreviated to `rw`) is invoked with `h : (p ∧ q) ↔ r`. This
cases Lean to replace `p ∧ q`, wherever it appears in the goal, with `r`.

The goal is reduced to `r ∧ q ∧ r ↔ r ∧ q ∧ r`, which Lean immediately closes.

Note that a one-line tactic proof can be written using `by` rather than enclosing the proof in
a `begin` … `end` block.
-/
example (h : (p ∧ q) ↔ r) : (p ∧ q) ∧ (q ∧ (p ∧ q)) ↔ r ∧ (q ∧ r) :=
by rw h

/-
Below, we use `and_iff_and` (the commutativity of `∧`) together with `and_assoc`
(the associativity of `∧`). Note that `and_iff_and r (q ∧ r)` is the result
`r ∧ (q ∧ r) ↔ (q ∧ r) ∧ r`.
-/
example (h : (p ∧ q) ↔ r) : (p ∧ q) ∧ (q ∧ (p ∧ q)) ↔ q ∧ (r ∧ r) :=
begin
  rw h,
  rw and_iff_and r (q ∧ r),
  rw and_assoc q r r,
end

/-
The `rw` tactic can often infer arguments to theorems. In the example above, we can omit
the arguments, leading to the simplified proof below.
-/
example (h : (p ∧ q) ↔ r) : (p ∧ q) ∧ (q ∧ (p ∧ q)) ↔ q ∧ (r ∧ r) :=
begin
  rw h,
  rw and_iff_and,
  rw and_assoc,
end

-- Several applications of `rw` can be written on one line.
example (h : (p ∧ q) ↔ r) : (p ∧ q) ∧ (q ∧ (p ∧ q)) ↔ q ∧ (r ∧ r) :=
begin
  rw [h, and_iff_and, and_assoc],
end

#check and_assoc

/-
We've seen how `rw` can rewrite a goal given a double implication. It can also rewrite a goal
given an equation.

Here, `ℤ` represents the type of integers and is written `\Z`.
-/
example (a b : ℤ) (h : a = b) : a + 2 = b + 2 :=
begin
  rw h,
end

/-
Given `h : a = b` or `h : a ↔ b`, we've seen that `rw h` replaces every occurrence of `a` in the
goal with `b`. We can ask for rewriting in the other direction by issuing `rw ←h`, where `←` is
written `\l`. This replaces every occurrence of `b` in the goal with `a`.
-/

/-
Here, we use `rw` with `←`. The `linarith` tactic can solve linear equations and inequalities.

In fact, `linarith` is clever enough to perform the rewriting *without* needing you to specify
it explicitly, so we could omit the line `rw ←h` below.
-/
example (a b : ℤ) (h : 3 + a = b) : b + 3 = a + 6 :=
begin
  rw ←h,
  linarith,
end

-- Exercise 036:
-- Use `rw` with `←` and the theorems we've proved to solve the following.
example : r ∧ (p ∧ q) ↔ (p ∧ r) ∧ q :=
begin
  sorry
end

end rewriting

section implicit_arguments

-- Exercise 037:
/-
Often, it's easier to write a theorem in a form that doesn't require arguments to be specified when
the theorem is applied. We do this by enclosing implicit arguments in braces `{` and `}`.
-/
theorem imp_trans {a b c : Prop} : (a → b) → (b → c) → (a → c) :=
begin
  sorry
end

-- Note how the proof of the following complicated result is simply `imp_trans`.
example : (p ∧ q → r) → (r → r ∧ q) → (p ∧ q → r ∧ q) :=
imp_trans

-- In tactic-form, the proof is `exact imp_trans`.
example : (p ∧ q → r) → (r → r ∧ q) → (p ∧ q → r ∧ q) :=
begin
  exact imp_trans
end

/-
As a practical example, we'll use the following result, `add_left_cancel`, which has type
  `∀ {a b c : ℤ}, a + b = a + c → b = c`.
-/
lemma add_left_cancel : ∀ {a b c : ℤ}, a + b = a + c → b = c :=
begin
  intros a b c,
  exact (add_right_inj a).mp
end

/-
We use this result below.
-/
example (x y : ℤ) (h : 2 + x = 2 + y) : x = y :=
add_left_cancel h

-- Exercise 038:
-- Check what the theorem `int.add_comm` asserts. You can prove the following use only this,
-- `int.add_left_cancel`, `have`, `exact`, `rw`, and `apply`.
example (s t u a b : ℤ) (h₁ : s + u = t + u) (h₂ : s + a = t + b) : a = b :=
begin
  have h₃ : u + s =  u + t,
  { sorry, },
  sorry
end

end implicit_arguments

/-
SUMMARY:

* Using the `rw` tactic to rewrite a goal using an equation or iff.
* Using `←` to use an equation or iff in the opposite direction.

* Implicit arguments to theorems using `{ … }`
-/

end mth1001


--- Implement my own mynat from the built-in natural numbers
open nat

-- add zero is used in the game
theorem add_zero (m : nat) : m + 0 = m := rfl
theorem one_eq_succ_zero : 1 = succ 0 := rfl
lemma mul_zero (m : ℕ) : m * 0 = 0 := rfl

-- tutorial world
lemma example1 (x y z : ℕ) : x * y + z = x * y + z :=
begin
refl,
end

lemma example2 (x y : ℕ) (h : y = x + 7) : 2 * y = 2 * (x + 7) :=
begin
rw h,
end

lemma example3 (a b : ℕ) (h : succ a = b) : succ(succ(a)) = succ(b) :=
begin
rw h,
end

-- addition world
lemma add_succ_zero (a : ℕ) : a + succ(0) = succ(a) :=
begin
rw add_succ,
rw add_zero,
end

lemma zero_add (n : ℕ) : 0 + n = n :=
begin
induction n with h hd,
rw add_zero,
rw add_succ,
rw hd,
end

lemma add_assoc (a b c : ℕ) : (a + b) + c = a + (b + c) :=
begin
induction c with h hd,
repeat {rw add_zero},
repeat {rw add_succ},
rw hd,
end

lemma succ_add' (a b : ℕ) : succ a + b = succ (a + b) :=
begin
induction b with h hd,
repeat {rw add_zero},
rw add_succ,
rw hd,
rw add_succ,
end

lemma add_comm (a b : ℕ) : a + b = b + a :=
begin
induction b with h hd,
rw add_zero,
rw zero_add,
rw add_succ,
rw hd,
rw succ_add',
end

theorem succ_eq_add_one' (n : ℕ) : succ n = n + 1 :=
begin
rw one_eq_succ_zero,
end

lemma add_right_comm (a b c : ℕ) : a + b + c = a + c + b :=
begin
induction a with h hd,
repeat {rw zero_add},
rw add_comm,
repeat {rw succ_add},
rw hd,
repeat {rw succ_add},
end

-- function world
example (P Q : Type) (p : P) (h : P → Q) : Q :=
begin
exact h(p),
end

example : ℕ → ℕ :=
begin
intro n,
exact n,
end

example (P Q R S T U: Type)
(p : P)
(h : P → Q)
(i : Q → R)
(j : Q → T)
(k : S → T)
(l : T → U)
: U :=
begin
have q := h(p),
have t : T := j(q),
have u : U := l(t),
exact u,
end

example (P Q R S T U: Type)
(p : P)
(h : P → Q)
(i : Q → R)
(j : Q → T)
(k : S → T)
(l : T → U)
: U :=
begin
have q := h(p),
have t : T := j(q),
have u : U := l(t),
exact u,
end

example (P Q : Type) : P → (Q → P) :=
begin
intros p q,
exact p,
end

example (P Q R : Type) : (P → (Q → R)) → ((P → Q) → (P → R)) :=
begin
intros p q r,
exact p(r)(q(r)),
end

example (P Q F : Type) : (P → Q) → ((Q → F) → (P → F)) :=
begin
intros p q r,
exact q(p(r)),
end

example (P Q : Type) : (P → Q) → ((Q → empty) → (P → empty)) :=
begin
intros p q r,
exact q(p(r)),
end

example (A B C D E F G H I J K L : Type)
(f1 : A → B) (f2 : B → E) (f3 : E → D) (f4 : D → A) (f5 : E → F)
(f6 : F → C) (f7 : B → C) (f8 : F → G) (f9 : G → J) (f10 : I → J)
(f11 : J → I) (f12 : I → H) (f13 : E → H) (f14 : H → K) (f15 : I → L)
 : A → L :=
begin
intro f,
apply f15,
apply f11,
apply f9,
apply f8,
apply f5,
apply f2,
apply f1,
exact f,
end

-- multiplication world
lemma zero_mul (m : ℕ) : 0 * m = 0 :=
begin
induction m with h hd,
rw mul_zero,
rw mul_succ,
rw add_zero,
rw hd,
end

lemma mul_one (m : ℕ) : m * 1 = m :=
begin
rw one_eq_succ_zero,
rw mul_succ,
rw mul_zero,
rw zero_add,
end

lemma one_mul (m : ℕ) : 1 * m = m :=
begin
induction m with h hd,
rw mul_zero,
rw mul_succ,
rw hd,
end

lemma mul_add (t a b : ℕ) : t * (a + b) = t * a + t * b :=
begin
induction b with h hd,
induction a with j jd,
induction t with k kd,
rw mul_zero,
rw add_zero,
rw mul_zero,
rw add_zero,
rw mul_zero,
rw add_zero,
rw add_succ,
rw mul_succ,
rw hd,
rw mul_succ,
rw add_assoc,
end

lemma mul_assoc (a b c : ℕ) : (a * b) * c = a * (b * c) :=
begin
induction c with h hd,
rw mul_zero,
rw mul_zero,
rw mul_zero,
rw mul_succ,
rw mul_succ,
rw hd,
rw mul_add,
end

lemma succ_mul' (a b : ℕ) : succ a * b = a * b + b :=
begin
induction b with h hd,
simp,
refl,
rw mul_succ,
rw hd,
rw mul_succ,
rw succ_eq_add_one',
rw succ_eq_add_one',
simp,
end

lemma add_mul (a b t : ℕ) : (a + b) * t = a * t + b * t :=
begin
induction t with h hd,
rw mul_zero,
rw mul_zero,
refl,
rw mul_succ,
rw hd,
rw succ_eq_add_one,
rw mul_add,
rw mul_add,
rw mul_one,
rw mul_one,
simp,
end

lemma mul_comm (a b : ℕ) : a * b = b * a :=
begin
induction a with h hd,
rw mul_zero,
rw zero_mul,
rw succ_mul',
rw hd,
rw mul_succ,
end

lemma mul_left_comm (a b c : ℕ) : a * (b * c) = b * (a * c) :=
begin
induction c with h hd,
rw mul_zero,
rw mul_zero,
rw mul_zero,
rw mul_succ,
rw mul_add,
rw hd,
rw succ_eq_add_one,
rw mul_add,
rw mul_add,
rw mul_one,
rw mul_comm,
refl,
end


/-
Copyright (c) 2020 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon
-/
import data.nat.basic
/-!
# Natural number logarithm

This file defines `log b n`, the logarithm of `n` with base `b`, to be the largest `k` such that
`b ^ k ≤ n`.

-/
namespace nat

/-- `log b n`, is the logarithm of natural number `n` in base `b`. It returns the largest `k : ℕ`
such that `b^k ≤ n`, so if `b^k = n`, it returns exactly `k`. -/
@[pp_nodot] def log (b : ℕ) : ℕ → ℕ
| n :=
  if h : b ≤ n ∧ 1 < b then
    have n / b < n,
      from div_lt_self
        (nat.lt_of_lt_of_le (lt_trans zero_lt_one h.2) h.1) h.2,
    log (n / b) + 1
  else 0

lemma log_eq_zero {b n : ℕ} (hnb : n < b ∨ b ≤ 1) : log b n = 0 :=
begin
  rw [or_iff_not_and_not, not_lt, not_le] at hnb,
  rw [log, ←ite_not, if_pos hnb],
end

lemma log_eq_zero_of_lt {b n : ℕ} (hn : n < b) : log b n = 0 :=
log_eq_zero $ or.inl hn

lemma log_eq_zero_of_le {b n : ℕ} (hb : b ≤ 1) : log b n = 0 :=
log_eq_zero $ or.inr hb

lemma log_zero_eq_zero {b : ℕ} : log b 0 = 0 :=
by { rw log, cases b; refl }

lemma log_one_eq_zero {b : ℕ} : log b 1 = 0 :=
if h : b ≤ 1 then
  log_eq_zero_of_le h
else
  log_eq_zero_of_lt (not_le.mp h)

lemma log_b_zero_eq_zero {n : ℕ} : log 0 n = 0 :=
log_eq_zero_of_le zero_le_one

lemma log_b_one_eq_zero {n : ℕ} : log 1 n = 0 :=
log_eq_zero_of_le rfl.ge

lemma pow_le_iff_le_log (x y : ℕ) {b} (hb : 1 < b) (hy : 1 ≤ y) :
  b^x ≤ y ↔ x ≤ log b y :=
begin
  induction y using nat.strong_induction_on with y ih
    generalizing x,
  rw [log], split_ifs,
  { have h'' : 0 < b := lt_of_le_of_lt (zero_le _) hb,
    cases h with h₀ h₁,
    rw [← nat.sub_le_right_iff_le_add,← ih (y / b),
          le_div_iff_mul_le _ _ h'',← pow_succ'],
    { cases x; simp [h₀,hy] },
    { apply div_lt_self; assumption },
    { rwa [le_div_iff_mul_le _ _ h'',one_mul], } },
  { replace h := lt_of_not_ge (not_and'.1 h hb),
    split; intros h',
    { have := lt_of_le_of_lt h' h,
      apply le_of_succ_le_succ,
      change x < 1, rw [← pow_lt_iff_lt_right hb,pow_one],
      exact this },
    { replace h' := le_antisymm h' (zero_le _),
      rw [h',pow_zero], exact hy} },
end

lemma log_pow (b x : ℕ) (hb : 1 < b) : log b (b ^ x) = x :=
eq_of_forall_le_iff $ λ z,
by { rwa [← pow_le_iff_le_log _ _ hb,pow_le_iff_le_right],
     rw ← pow_zero b, apply pow_le_pow_of_le_right,
     apply lt_of_le_of_lt (zero_le _) hb, apply zero_le }

lemma pow_succ_log_gt_self (b x : ℕ) (hb : 1 < b) (hy : 1 ≤ x) :
  x < b ^ succ (log b x) :=
begin
  apply lt_of_not_ge,
  rw [(≥),pow_le_iff_le_log _ _ hb hy],
  apply not_le_of_lt, apply lt_succ_self,
end

lemma pow_log_le_self (b x : ℕ) (hb : 1 < b) (hx : 1 ≤ x) : b ^ log b x ≤ x :=
by rw [pow_le_iff_le_log _ _ hb hx]

lemma log_le_log_of_le {b n m : ℕ} (h : n ≤ m) : log b n ≤ log b m :=
begin
  cases le_or_lt b 1 with hb hb,
  { rw log_eq_zero_of_le hb, exact zero_le _ },
  { cases eq_zero_or_pos n with hn hn,
    { rw [hn, log_zero_eq_zero], exact zero_le _ },
    { rw ←pow_le_iff_le_log _ _ hb (lt_of_lt_of_le hn h),
      exact (pow_log_le_self b n hb hn).trans h } }
end

lemma log_le_log_succ {b n : ℕ} : log b n ≤ log b n.succ :=
log_le_log_of_le $ le_succ n

lemma log_mono {b : ℕ} : monotone (λ n : ℕ, log b n) :=
monotone_of_monotone_nat $ λ n, log_le_log_succ

end nat


import tactic.interactive tactic.ring

inductive is_even : ℕ → Prop
| zero : is_even 0
| step {n} : is_even n → is_even (n + 2)

example : is_even 4 :=
begin
repeat { apply is_even.step },
apply is_even.zero,
end

example : ¬ is_even 5 :=
begin
intro even_five,
cases even_five with _ even_three,
cases even_three with _ even_one,
cases even_one,
end

lemma even_if_double {n} : is_even (2*n) :=
begin
induction n with d hd,
{ exact is_even.zero },
{ show is_even (2 * (d + 1)),
  rw mul_add,
  apply is_even.step,
  assumption,
 },
end

lemma double_if_even {n} : is_even n → ∃ m, n = 2*m :=
begin
intro h, induction h with d Hd ih,
{ use 0,
  refl
},
{ cases ih with n Hn,
  use (n + 1),
  rw Hn,
  refl,
 }
end

lemma even_iff_double {n} : is_even n ↔ ∃ m, n = 2*m :=
begin
split, apply double_if_even,
intro h, cases h with d Hd, rw Hd,
  apply even_if_double
end

inductive is_odd : ℕ → Prop
| one : is_odd 1
| step {n} : is_odd n → is_odd (n + 2)

lemma odd_iff_double_add_one {n} : is_odd n ↔ ∃ m, n = 2*m + 1 :=
sorry

lemma even_of_not_odd : ∀ n,
¬ is_odd n → is_even n
| 0 h := is_even.zero
| 1 h := begin exfalso, apply h, constructor end
| (n+2) h :=
  have ih : _, from even_of_not_odd n,
  begin
  constructor,
  apply ih,
  intro hn,
  apply h,
  constructor, assumption,
  end

#print even_of_not_odd

lemma not_odd_of_even : ∀ n, is_even n → ¬ is_odd n :=
begin
intros n h,
induction h with m m hm,
{intro h, cases h},
{intro h, apply hm, cases h, assumption},
end

lemma even_iff_not_odd : ∀ n, is_even n ↔ ¬ is_odd n :=
begin
intro n, split, apply not_odd_of_even, apply even_of_not_odd
end

lemma odd_square (n : ℕ) : is_odd n → is_odd (n*n) :=
begin
simp [odd_iff_double_add_one],
intro m,
intros  h,
use 2*m + 2*m*m, subst h, ring,
end

lemma even_square (n : ℕ) : is_even (n*n) → is_even n :=
begin
conv in (is_even (n*n)) { simp [even_iff_not_odd] },
simp [even_iff_not_odd],
intros h1 h2, apply h1,
apply odd_square, assumption
end

--set_option pp.all true
#check (7 : ℤ)


/-
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

! This file was ported from Lean 3 source module analysis.mean_inequalities
! leanprover-community/mathlib commit a148d797a1094ab554ad4183a4ad6f130358ef64
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.Convex.SpecificFunctions
import Mathbin.Data.Real.ConjugateExponents

/-!
# Mean value inequalities

In this file we prove several inequalities for finite sums, including AM-GM inequality,
Young's inequality, Hölder inequality, and Minkowski inequality. Versions for integrals of some of
these inequalities are available in `measure_theory.mean_inequalities`.

## 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.

### 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 is then used to prove Hölder's
inequality (see below).

### 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 our proof we prenormalize both vectors,
then apply Young's inequality to each $a_ib_i$. Another possible proof would be to deduce this
inequality from the generalized mean inequality for well-chosen vectors and weights.

### 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.

## 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.

-/

universe u v

open Finset

open Classical BigOperators NNReal ENNReal

noncomputable section

variable {ι : Type u} (s : Finset ι)

section GeomMeanLeArithMean

/-! ### AM-GM inequality -/

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 :=
  by
  -- 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 fun 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, Classical.not_not] at A
    have := convex_on_exp.map_sum_le hw hw' fun 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] <;> intro 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]
#align real.geom_mean_le_arith_mean_weighted Real.geom_mean_le_arith_mean_weighted

theorem geom_mean_weighted_of_constant (w z : ι → ℝ) (x : ℝ) (hw : ∀ i ∈ s, 0 ≤ w i)
    (hw' : (∑ i in s, w i) = 1) (hz : ∀ i ∈ s, 0 ≤ z i) (hx : ∀ i ∈ s, w i ≠ 0 → z i = x) :
    (∏ i in s, z i ^ w i) = x :=
  calc
    (∏ i in s, z i ^ w i) = ∏ i in s, x ^ w i :=
      by
      refine' prod_congr rfl fun i hi => _
      cases' eq_or_ne (w i) 0 with h₀ h₀
      · rw [h₀, rpow_zero, rpow_zero]
      · rw [hx i hi h₀]
    _ = x := by
      rw [← rpow_sum_of_nonneg _ hw, hw', rpow_one]
      have : (∑ i in s, w i) ≠ 0 := by
        rw [hw']
        exact one_ne_zero
      obtain ⟨i, his, hi⟩ := exists_ne_zero_of_sum_ne_zero this
      rw [← hx i his hi]
      exact hz i his

#align real.geom_mean_weighted_of_constant Real.geom_mean_weighted_of_constant

theorem arith_mean_weighted_of_constant (w z : ι → ℝ) (x : ℝ) (hw' : (∑ i in s, w i) = 1)
    (hx : ∀ i ∈ s, w i ≠ 0 → z i = x) : (∑ i in s, w i * z i) = x :=
  calc
    (∑ i in s, w i * z i) = ∑ i in s, w i * x :=
      by
      refine' sum_congr rfl fun i hi => _
      cases' eq_or_ne (w i) 0 with hwi hwi
      · rw [hwi, MulZeroClass.zero_mul, MulZeroClass.zero_mul]
      · rw [hx i hi hwi]
    _ = x := by rw [← sum_mul, hw', one_mul]

#align real.arith_mean_weighted_of_constant Real.arith_mean_weighted_of_constant

theorem geom_mean_eq_arith_mean_weighted_of_constant (w z : ι → ℝ) (x : ℝ) (hw : ∀ i ∈ s, 0 ≤ w i)
    (hw' : (∑ i in s, w i) = 1) (hz : ∀ i ∈ s, 0 ≤ z i) (hx : ∀ i ∈ s, w i ≠ 0 → z i = x) :
    (∏ i in s, z i ^ w i) = ∑ i in s, w i * z i := by
  rw [geom_mean_weighted_of_constant, arith_mean_weighted_of_constant] <;> assumption
#align real.geom_mean_eq_arith_mean_weighted_of_constant Real.geom_mean_eq_arith_mean_weighted_of_constant

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 _ _ _ (fun i _ => (w i).coe_nonneg)
      (by assumption_mod_cast) fun i _ => (z i).coe_nonneg
#align nnreal.geom_mean_le_arith_mean_weighted NNReal.geom_mean_le_arith_mean_weighted

/-- 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, Finset.prod_empty, Finset.sum_empty,
    Fintype.univ_of_isEmpty, Fin.cons_succ, Fin.cons_zero, add_zero, mul_one] using
    geom_mean_le_arith_mean_weighted univ ![w₁, w₂] ![p₁, p₂]
#align nnreal.geom_mean_le_arith_mean2_weighted NNReal.geom_mean_le_arith_mean2_weighted

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, Finset.prod_empty, Finset.sum_empty,
    Fintype.univ_of_isEmpty, Fin.cons_succ, Fin.cons_zero, add_zero, mul_one, ← add_assoc,
    mul_assoc] using geom_mean_le_arith_mean_weighted univ ![w₁, w₂, w₃] ![p₁, p₂, p₃]
#align nnreal.geom_mean_le_arith_mean3_weighted NNReal.geom_mean_le_arith_mean3_weighted

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, Finset.prod_empty, Finset.sum_empty,
    Fintype.univ_of_isEmpty, Fin.cons_succ, Fin.cons_zero, add_zero, mul_one, ← add_assoc,
    mul_assoc] using geom_mean_le_arith_mean_weighted univ ![w₁, w₂, w₃, w₄] ![p₁, p₂, p₃, p₄]
#align nnreal.geom_mean_le_arith_mean4_weighted NNReal.geom_mean_le_arith_mean4_weighted

end NNReal

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
#align real.geom_mean_le_arith_mean2_weighted Real.geom_mean_le_arith_mean2_weighted

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
#align real.geom_mean_le_arith_mean3_weighted Real.geom_mean_le_arith_mean3_weighted

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
#align real.geom_mean_le_arith_mean4_weighted Real.geom_mean_le_arith_mean4_weighted

end Real

end GeomMeanLeArithMean

section Young

/-! ### Young's inequality -/

namespace Real

/-- 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.IsConjugateExponent 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
#align real.young_inequality_of_nonneg Real.young_inequality_of_nonneg

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

#align real.young_inequality Real.young_inequality

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⟩
#align nnreal.young_inequality NNReal.young_inequality

/-- Young's inequality, `ℝ≥0` version with real conjugate exponents. -/
theorem young_inequality_real (a b : ℝ≥0) {p q : ℝ} (hpq : p.IsConjugateExponent q) :
    a * b ≤ a ^ p / Real.toNNReal p + b ^ q / Real.toNNReal q :=
  by
  nth_rw 1 [← Real.coe_toNNReal p hpq.nonneg]
  nth_rw 1 [← Real.coe_toNNReal q hpq.symm.nonneg]
  exact young_inequality a b hpq.one_lt_nnreal hpq.inv_add_inv_conj_nnreal
#align nnreal.young_inequality_real NNReal.young_inequality_real

end NNReal

namespace ENNReal

/-- Young's inequality, `ℝ≥0∞` version with real conjugate exponents. -/
theorem young_inequality (a b : ℝ≥0∞) {p q : ℝ} (hpq : p.IsConjugateExponent q) :
    a * b ≤ a ^ p / ENNReal.ofReal p + b ^ q / ENNReal.ofReal q :=
  by
  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.ofReal, ENNReal.ofReal, ←
    @coe_div (Real.toNNReal p) _ (by simp [hpq.pos]), ←
    @coe_div (Real.toNNReal q) _ (by simp [hpq.symm.pos]), ← coe_add, coe_le_coe]
  exact NNReal.young_inequality_real a.to_nnreal b.to_nnreal hpq
#align ennreal.young_inequality ENNReal.young_inequality

end ENNReal

end Young

section HolderMinkowski

/-! ### Hölder's and Minkowski's inequalities -/

namespace NNReal

private theorem inner_le_Lp_mul_Lp_of_norm_le_one (f g : ι → ℝ≥0) {p q : ℝ}
    (hpq : p.IsConjugateExponent q) (hf : (∑ i in s, f i ^ p) ≤ 1) (hg : (∑ i in s, g i ^ q) ≤ 1) :
    (∑ i in s, f i * g i) ≤ 1 :=
  by
  have hp_ne_zero : Real.toNNReal p ≠ 0 := (zero_lt_one.trans hpq.one_lt_nnreal).Ne.symm
  have hq_ne_zero : Real.toNNReal q ≠ 0 := (zero_lt_one.trans hpq.symm.one_lt_nnreal).Ne.symm
  calc
    (∑ i in s, f i * g i) ≤ ∑ i in s, f i ^ p / Real.toNNReal p + g i ^ q / Real.toNNReal q :=
      Finset.sum_le_sum fun i his => young_inequality_real (f i) (g i) hpq
    _ = (∑ i in s, f i ^ p) / Real.toNNReal p + (∑ i in s, g i ^ q) / Real.toNNReal q := by
      rw [sum_add_distrib, sum_div, sum_div]
    _ ≤ 1 / Real.toNNReal p + 1 / Real.toNNReal q :=
      by
      refine' add_le_add _ _
      · rwa [div_le_iff hp_ne_zero, div_mul_cancel _ hp_ne_zero]
      · rwa [div_le_iff hq_ne_zero, div_mul_cancel _ hq_ne_zero]
    _ = 1 := hpq.inv_add_inv_conj_nnreal

#align nnreal.inner_le_Lp_mul_Lp_of_norm_le_one nnreal.inner_le_Lp_mul_Lp_of_norm_le_one

private theorem inner_le_Lp_mul_Lp_of_norm_eq_zero (f g : ι → ℝ≥0) {p q : ℝ}
    (hpq : p.IsConjugateExponent q) (hf : (∑ i in s, f i ^ p) = 0) :
    (∑ i in s, f i * g i) ≤ (∑ i in s, f i ^ p) ^ (1 / p) * (∑ i in s, g i ^ q) ^ (1 / q) :=
  by
  simp only [hf, hpq.ne_zero, one_div, sum_eq_zero_iff, zero_rpow, MulZeroClass.zero_mul,
    inv_eq_zero, Ne.def, not_false_iff, le_zero_iff, mul_eq_zero]
  intro i his
  left
  rw [sum_eq_zero_iff] at hf
  exact (rpow_eq_zero_iff.mp (hf i his)).left
#align nnreal.inner_le_Lp_mul_Lp_of_norm_eq_zero nnreal.inner_le_Lp_mul_Lp_of_norm_eq_zero

/-- 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.IsConjugateExponent q) :
    (∑ i in s, f i * g i) ≤ (∑ i in s, f i ^ p) ^ (1 / p) * (∑ i in s, g i ^ q) ^ (1 / q) :=
  by
  by_cases hF_zero : (∑ i in s, f i ^ p) = 0
  · exact inner_le_Lp_mul_Lp_of_norm_eq_zero s f g hpq hF_zero
  by_cases hG_zero : (∑ i in s, g i ^ q) = 0
  ·
    calc
      (∑ i in s, f i * g i) = ∑ i in s, g i * f i :=
        by
        congr with i
        rw [mul_comm]
      _ ≤ (∑ i in s, g i ^ q) ^ (1 / q) * (∑ i in s, f i ^ p) ^ (1 / p) :=
        (inner_le_Lp_mul_Lp_of_norm_eq_zero s g f hpq.symm hG_zero)
      _ = (∑ i in s, f i ^ p) ^ (1 / p) * (∑ i in s, g i ^ q) ^ (1 / q) := mul_comm _ _

  let f' i := f i / (∑ i in s, f i ^ p) ^ (1 / p)
  let g' i := g i / (∑ i in s, g i ^ q) ^ (1 / q)
  suffices (∑ i in s, f' i * g' i) ≤ 1
    by
    simp_rw [f', g', div_mul_div_comm, ← sum_div] at this
    rwa [div_le_iff, one_mul] at this
    refine' mul_ne_zero _ _
    · rw [Ne.def, rpow_eq_zero_iff, not_and_or]
      exact Or.inl hF_zero
    · rw [Ne.def, rpow_eq_zero_iff, not_and_or]
      exact Or.inl hG_zero
  refine' inner_le_Lp_mul_Lp_of_norm_le_one s f' g' hpq (le_of_eq _) (le_of_eq _)
  ·
    simp_rw [f', div_rpow, ← sum_div, ← rpow_mul, one_div, inv_mul_cancel hpq.ne_zero, rpow_one,
      div_self hF_zero]
  ·
    simp_rw [g', div_rpow, ← sum_div, ← rpow_mul, one_div, inv_mul_cancel hpq.symm.ne_zero,
      rpow_one, div_self hG_zero]
#align nnreal.inner_le_Lp_mul_Lq NNReal.inner_le_Lp_mul_Lq

/-- 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. A version for `nnreal`-valued
functions. For an alternative version, convenient if the infinite sums are already expressed as
`p`-th powers, see `inner_le_Lp_mul_Lq_has_sum`. -/
theorem inner_le_Lp_mul_Lq_tsum {f g : ι → ℝ≥0} {p q : ℝ} (hpq : p.IsConjugateExponent q)
    (hf : Summable fun i => f i ^ p) (hg : Summable fun i => g i ^ q) :
    (Summable fun i => f i * g i) ∧
      (∑' i, f i * g i) ≤ (∑' i, f i ^ p) ^ (1 / p) * (∑' i, g i ^ q) ^ (1 / q) :=
  by
  have H₁ :
    ∀ s : Finset ι, (∑ i in s, f i * g i) ≤ (∑' i, f i ^ p) ^ (1 / p) * (∑' i, g i ^ q) ^ (1 / q) :=
    by
    intro s
    refine' le_trans (inner_le_Lp_mul_Lq s f g hpq) (mul_le_mul _ _ bot_le bot_le)
    · rw [NNReal.rpow_le_rpow_iff (one_div_pos.mpr hpq.pos)]
      exact sum_le_tsum _ (fun _ _ => zero_le _) hf
    · rw [NNReal.rpow_le_rpow_iff (one_div_pos.mpr hpq.symm.pos)]
      exact sum_le_tsum _ (fun _ _ => zero_le _) hg
  have bdd : BddAbove (Set.range fun s => ∑ i in s, f i * g i) :=
    by
    refine' ⟨(∑' i, f i ^ p) ^ (1 / p) * (∑' i, g i ^ q) ^ (1 / q), _⟩
    rintro a ⟨s, rfl⟩
    exact H₁ s
  have H₂ : Summable _ := (hasSum_of_isLUB _ (isLUB_csupᵢ bdd)).Summable
  exact ⟨H₂, tsum_le_of_sum_le H₂ H₁⟩
#align nnreal.inner_le_Lp_mul_Lq_tsum NNReal.inner_le_Lp_mul_Lq_tsum

theorem summable_mul_of_Lp_Lq {f g : ι → ℝ≥0} {p q : ℝ} (hpq : p.IsConjugateExponent q)
    (hf : Summable fun i => f i ^ p) (hg : Summable fun i => g i ^ q) :
    Summable fun i => f i * g i :=
  (inner_le_Lp_mul_Lq_tsum hpq hf hg).1
#align nnreal.summable_mul_of_Lp_Lq NNReal.summable_mul_of_Lp_Lq

theorem inner_le_Lp_mul_Lq_tsum' {f g : ι → ℝ≥0} {p q : ℝ} (hpq : p.IsConjugateExponent q)
    (hf : Summable fun i => f i ^ p) (hg : Summable fun i => g i ^ q) :
    (∑' i, f i * g i) ≤ (∑' i, f i ^ p) ^ (1 / p) * (∑' i, g i ^ q) ^ (1 / q) :=
  (inner_le_Lp_mul_Lq_tsum hpq hf hg).2
#align nnreal.inner_le_Lp_mul_Lq_tsum' NNReal.inner_le_Lp_mul_Lq_tsum'

/-- 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. A version for `nnreal`-valued
functions. For an alternative version, convenient if the infinite sums are not already expressed as
`p`-th powers, see `inner_le_Lp_mul_Lq_tsum`.  -/
theorem inner_le_Lp_mul_Lq_hasSum {f g : ι → ℝ≥0} {A B : ℝ≥0} {p q : ℝ}
    (hpq : p.IsConjugateExponent q) (hf : HasSum (fun i => f i ^ p) (A ^ p))
    (hg : HasSum (fun i => g i ^ q) (B ^ q)) : ∃ C, C ≤ A * B ∧ HasSum (fun i => f i * g i) C :=
  by
  obtain ⟨H₁, H₂⟩ := inner_le_Lp_mul_Lq_tsum hpq hf.summable hg.summable
  have hA : A = (∑' i : ι, f i ^ p) ^ (1 / p) := by rw [hf.tsum_eq, rpow_inv_rpow_self hpq.ne_zero]
  have hB : B = (∑' i : ι, g i ^ q) ^ (1 / q) := by
    rw [hg.tsum_eq, rpow_inv_rpow_self hpq.symm.ne_zero]
  refine' ⟨∑' i, f i * g i, _, _⟩
  · simpa [hA, hB] using H₂
  · simpa only [rpow_self_rpow_inv hpq.ne_zero] using H₁.has_sum
#align nnreal.inner_le_Lp_mul_Lq_has_sum NNReal.inner_le_Lp_mul_Lq_hasSum

/-- For `1 ≤ p`, the `p`-th power of the sum of `f i` is bounded above by a constant times the
sum of the `p`-th powers of `f i`. Version for sums over finite sets, with `ℝ≥0`-valued functions.
-/
theorem rpow_sum_le_const_mul_sum_rpow (f : ι → ℝ≥0) {p : ℝ} (hp : 1 ≤ p) :
    (∑ i in s, f i) ^ p ≤ card s ^ (p - 1) * ∑ i in s, f i ^ p :=
  by
  cases' eq_or_lt_of_le hp with hp hp
  · simp [← hp]
  let q : ℝ := p / (p - 1)
  have hpq : p.is_conjugate_exponent q := by rw [Real.isConjugateExponent_iff hp]
  have hp₁ : 1 / p * p = 1 := one_div_mul_cancel hpq.ne_zero
  have hq : 1 / q * p = p - 1 := by
    rw [← hpq.div_conj_eq_sub_one]
    ring
  simpa only [NNReal.mul_rpow, ← NNReal.rpow_mul, hp₁, hq, one_mul, one_rpow, rpow_one,
    Pi.one_apply, sum_const, Nat.smul_one_eq_coe] using
    NNReal.rpow_le_rpow (inner_le_Lp_mul_Lq s 1 f hpq.symm) hpq.nonneg
#align nnreal.rpow_sum_le_const_mul_sum_rpow NNReal.rpow_sum_le_const_mul_sum_rpow

/-- 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 isGreatest_Lp (f : ι → ℝ≥0) {p q : ℝ} (hpq : p.IsConjugateExponent q) :
    IsGreatest ((fun 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)) :=
  by
  constructor
  · use fun 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 :=
        by
        refine' fun y => mul_div_cancel_left_of_imp fun h => _
        simpa [h, hpq.ne_zero]
      simp only [Set.mem_setOf_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_iff, ← 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
  · rintro _ ⟨g, hg, rfl⟩
    apply le_trans (inner_le_Lp_mul_Lq s f g hpq)
    simpa only [mul_one] using
      mul_le_mul_left' (NNReal.rpow_le_one hg (le_of_lt hpq.symm.one_div_pos)) _
#align nnreal.is_greatest_Lp NNReal.isGreatest_Lp

/-- 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) :=
  by
  -- 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.isConjugateExponent_conjugateExponent 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⟩)
#align nnreal.Lp_add_le NNReal.Lp_add_le

/-- Minkowski inequality: the `L_p` seminorm of the infinite sum of two vectors is less than or
equal to the infinite sum of the `L_p`-seminorms of the summands, if these infinite sums both
exist. A version for `nnreal`-valued functions. For an alternative version, convenient if the
infinite sums are already expressed as `p`-th powers, see `Lp_add_le_has_sum_of_nonneg`. -/
theorem Lp_add_le_tsum {f g : ι → ℝ≥0} {p : ℝ} (hp : 1 ≤ p) (hf : Summable fun i => f i ^ p)
    (hg : Summable fun i => g i ^ p) :
    (Summable fun i => (f i + g i) ^ p) ∧
      (∑' i, (f i + g i) ^ p) ^ (1 / p) ≤ (∑' i, f i ^ p) ^ (1 / p) + (∑' i, g i ^ p) ^ (1 / p) :=
  by
  have pos : 0 < p := lt_of_lt_of_le zero_lt_one hp
  have H₁ :
    ∀ s : Finset ι,
      (∑ i in s, (f i + g i) ^ p) ≤ ((∑' i, f i ^ p) ^ (1 / p) + (∑' i, g i ^ p) ^ (1 / p)) ^ p :=
    by
    intro s
    rw [← NNReal.rpow_one_div_le_iff Pos]
    refine' le_trans (Lp_add_le s f g hp) (add_le_add _ _) <;>
        rw [NNReal.rpow_le_rpow_iff (one_div_pos.mpr Pos)] <;>
      refine' sum_le_tsum _ (fun _ _ => zero_le _) _
    exacts[hf, hg]
  have bdd : BddAbove (Set.range fun s => ∑ i in s, (f i + g i) ^ p) :=
    by
    refine' ⟨((∑' i, f i ^ p) ^ (1 / p) + (∑' i, g i ^ p) ^ (1 / p)) ^ p, _⟩
    rintro a ⟨s, rfl⟩
    exact H₁ s
  have H₂ : Summable _ := (hasSum_of_isLUB _ (isLUB_csupᵢ bdd)).Summable
  refine' ⟨H₂, _⟩
  rw [NNReal.rpow_one_div_le_iff Pos]
  refine' tsum_le_of_sum_le H₂ H₁
#align nnreal.Lp_add_le_tsum NNReal.Lp_add_le_tsum

theorem summable_Lp_add {f g : ι → ℝ≥0} {p : ℝ} (hp : 1 ≤ p) (hf : Summable fun i => f i ^ p)
    (hg : Summable fun i => g i ^ p) : Summable fun i => (f i + g i) ^ p :=
  (Lp_add_le_tsum hp hf hg).1
#align nnreal.summable_Lp_add NNReal.summable_Lp_add

theorem Lp_add_le_tsum' {f g : ι → ℝ≥0} {p : ℝ} (hp : 1 ≤ p) (hf : Summable fun i => f i ^ p)
    (hg : Summable fun i => g i ^ p) :
    (∑' i, (f i + g i) ^ p) ^ (1 / p) ≤ (∑' i, f i ^ p) ^ (1 / p) + (∑' i, g i ^ p) ^ (1 / p) :=
  (Lp_add_le_tsum hp hf hg).2
#align nnreal.Lp_add_le_tsum' NNReal.Lp_add_le_tsum'

/-- Minkowski inequality: the `L_p` seminorm of the infinite sum of two vectors is less than or
equal to the infinite sum of the `L_p`-seminorms of the summands, if these infinite sums both
exist. A version for `nnreal`-valued functions. For an alternative version, convenient if the
infinite sums are not already expressed as `p`-th powers, see `Lp_add_le_tsum_of_nonneg`.  -/
theorem Lp_add_le_hasSum {f g : ι → ℝ≥0} {A B : ℝ≥0} {p : ℝ} (hp : 1 ≤ p)
    (hf : HasSum (fun i => f i ^ p) (A ^ p)) (hg : HasSum (fun i => g i ^ p) (B ^ p)) :
    ∃ C, C ≤ A + B ∧ HasSum (fun i => (f i + g i) ^ p) (C ^ p) :=
  by
  have hp' : p ≠ 0 := (lt_of_lt_of_le zero_lt_one hp).ne'
  obtain ⟨H₁, H₂⟩ := Lp_add_le_tsum hp hf.summable hg.summable
  have hA : A = (∑' i : ι, f i ^ p) ^ (1 / p) := by rw [hf.tsum_eq, rpow_inv_rpow_self hp']
  have hB : B = (∑' i : ι, g i ^ p) ^ (1 / p) := by rw [hg.tsum_eq, rpow_inv_rpow_self hp']
  refine' ⟨(∑' i, (f i + g i) ^ p) ^ (1 / p), _, _⟩
  · simpa [hA, hB] using H₂
  · simpa only [rpow_self_rpow_inv hp'] using H₁.has_sum
#align nnreal.Lp_add_le_has_sum NNReal.Lp_add_le_hasSum

end NNReal

namespace Real

variable (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 : IsConjugateExponent p q) :
    (∑ i in s, f i * g i) ≤ (∑ i in s, |f i| ^ p) ^ (1 / p) * (∑ i in s, |g i| ^ q) ^ (1 / q) :=
  by
  have :=
    NNReal.coe_le_coe.2
      (NNReal.inner_le_Lp_mul_Lq s (fun i => ⟨_, abs_nonneg (f i)⟩) (fun i => ⟨_, abs_nonneg (g i)⟩)
        hpq)
  push_cast at this
  refine' le_trans (sum_le_sum fun i hi => _) this
  simp only [← abs_mul, le_abs_self]
#align real.inner_le_Lp_mul_Lq Real.inner_le_Lp_mul_Lq

/-- For `1 ≤ p`, the `p`-th power of the sum of `f i` is bounded above by a constant times the
sum of the `p`-th powers of `f i`. Version for sums over finite sets, with `ℝ`-valued functions. -/
theorem rpow_sum_le_const_mul_sum_rpow (hp : 1 ≤ p) :
    (∑ i in s, |f i|) ^ p ≤ card s ^ (p - 1) * ∑ i in s, |f i| ^ p :=
  by
  have :=
    NNReal.coe_le_coe.2
      (NNReal.rpow_sum_le_const_mul_sum_rpow s (fun i => ⟨_, abs_nonneg (f i)⟩) hp)
  push_cast at this
  exact this
#align real.rpow_sum_le_const_mul_sum_rpow Real.rpow_sum_le_const_mul_sum_rpow

-- for some reason `exact_mod_cast` can't replace this argument
/-- 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, |f i + g i| ^ p) ^ (1 / p) ≤
      (∑ i in s, |f i| ^ p) ^ (1 / p) + (∑ i in s, |g i| ^ p) ^ (1 / p) :=
  by
  have :=
    NNReal.coe_le_coe.2
      (NNReal.Lp_add_le s (fun i => ⟨_, abs_nonneg (f i)⟩) (fun i => ⟨_, abs_nonneg (g i)⟩) hp)
  push_cast at this
  refine' le_trans (rpow_le_rpow _ (sum_le_sum fun i hi => _) _) this <;>
    simp [sum_nonneg, rpow_nonneg_of_nonneg, abs_nonneg, le_trans zero_le_one hp, abs_add,
      rpow_le_rpow]
#align real.Lp_add_le Real.Lp_add_le

variable {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 : IsConjugateExponent 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 <;> intro i hi <;>
    simp only [abs_of_nonneg, hf i hi, hg i hi]
#align real.inner_le_Lp_mul_Lq_of_nonneg Real.inner_le_Lp_mul_Lq_of_nonneg

/-- 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. A version for `ℝ`-valued functions.
For an alternative version, convenient if the infinite sums are already expressed as `p`-th powers,
see `inner_le_Lp_mul_Lq_has_sum_of_nonneg`. -/
theorem inner_le_Lp_mul_Lq_tsum_of_nonneg (hpq : p.IsConjugateExponent q) (hf : ∀ i, 0 ≤ f i)
    (hg : ∀ i, 0 ≤ g i) (hf_sum : Summable fun i => f i ^ p) (hg_sum : Summable fun i => g i ^ q) :
    (Summable fun i => f i * g i) ∧
      (∑' i, f i * g i) ≤ (∑' i, f i ^ p) ^ (1 / p) * (∑' i, g i ^ q) ^ (1 / q) :=
  by
  lift f to ι → ℝ≥0 using hf
  lift g to ι → ℝ≥0 using hg
  norm_cast  at *
  exact NNReal.inner_le_Lp_mul_Lq_tsum hpq hf_sum hg_sum
#align real.inner_le_Lp_mul_Lq_tsum_of_nonneg Real.inner_le_Lp_mul_Lq_tsum_of_nonneg

theorem summable_mul_of_Lp_Lq_of_nonneg (hpq : p.IsConjugateExponent q) (hf : ∀ i, 0 ≤ f i)
    (hg : ∀ i, 0 ≤ g i) (hf_sum : Summable fun i => f i ^ p) (hg_sum : Summable fun i => g i ^ q) :
    Summable fun i => f i * g i :=
  (inner_le_Lp_mul_Lq_tsum_of_nonneg hpq hf hg hf_sum hg_sum).1
#align real.summable_mul_of_Lp_Lq_of_nonneg Real.summable_mul_of_Lp_Lq_of_nonneg

theorem inner_le_Lp_mul_Lq_tsum_of_nonneg' (hpq : p.IsConjugateExponent q) (hf : ∀ i, 0 ≤ f i)
    (hg : ∀ i, 0 ≤ g i) (hf_sum : Summable fun i => f i ^ p) (hg_sum : Summable fun i => g i ^ q) :
    (∑' i, f i * g i) ≤ (∑' i, f i ^ p) ^ (1 / p) * (∑' i, g i ^ q) ^ (1 / q) :=
  (inner_le_Lp_mul_Lq_tsum_of_nonneg hpq hf hg hf_sum hg_sum).2
#align real.inner_le_Lp_mul_Lq_tsum_of_nonneg' Real.inner_le_Lp_mul_Lq_tsum_of_nonneg'

/-- 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. A version for `nnreal`-valued
functions. For an alternative version, convenient if the infinite sums are not already expressed as
`p`-th powers, see `inner_le_Lp_mul_Lq_tsum_of_nonneg`.  -/
theorem inner_le_Lp_mul_Lq_hasSum_of_nonneg (hpq : p.IsConjugateExponent q) {A B : ℝ} (hA : 0 ≤ A)
    (hB : 0 ≤ B) (hf : ∀ i, 0 ≤ f i) (hg : ∀ i, 0 ≤ g i)
    (hf_sum : HasSum (fun i => f i ^ p) (A ^ p)) (hg_sum : HasSum (fun i => g i ^ q) (B ^ q)) :
    ∃ C : ℝ, 0 ≤ C ∧ C ≤ A * B ∧ HasSum (fun i => f i * g i) C :=
  by
  lift f to ι → ℝ≥0 using hf
  lift g to ι → ℝ≥0 using hg
  lift A to ℝ≥0 using hA
  lift B to ℝ≥0 using hB
  norm_cast  at hf_sum hg_sum
  obtain ⟨C, hC, H⟩ := NNReal.inner_le_Lp_mul_Lq_hasSum hpq hf_sum hg_sum
  refine' ⟨C, C.prop, hC, _⟩
  norm_cast
  exact H
#align real.inner_le_Lp_mul_Lq_has_sum_of_nonneg Real.inner_le_Lp_mul_Lq_hasSum_of_nonneg

/-- For `1 ≤ p`, the `p`-th power of the sum of `f i` is bounded above by a constant times the
sum of the `p`-th powers of `f i`. Version for sums over finite sets, with nonnegative `ℝ`-valued
functions. -/
theorem rpow_sum_le_const_mul_sum_rpow_of_nonneg (hp : 1 ≤ p) (hf : ∀ i ∈ s, 0 ≤ f i) :
    (∑ i in s, f i) ^ p ≤ card s ^ (p - 1) * ∑ i in s, f i ^ p := by
  convert rpow_sum_le_const_mul_sum_rpow s f hp using 2 <;> apply sum_congr rfl <;> intro i hi <;>
    simp only [abs_of_nonneg, hf i hi]
#align real.rpow_sum_le_const_mul_sum_rpow_of_nonneg Real.rpow_sum_le_const_mul_sum_rpow_of_nonneg

/-- 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 `ℝ`-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 <;>
      intro i hi <;>
    simp only [abs_of_nonneg, hf i hi, hg i hi, add_nonneg]
#align real.Lp_add_le_of_nonneg Real.Lp_add_le_of_nonneg

/-- Minkowski inequality: the `L_p` seminorm of the infinite sum of two vectors is less than or
equal to the infinite sum of the `L_p`-seminorms of the summands, if these infinite sums both
exist. A version for `ℝ`-valued functions. For an alternative version, convenient if the infinite
sums are already expressed as `p`-th powers, see `Lp_add_le_has_sum_of_nonneg`. -/
theorem Lp_add_le_tsum_of_nonneg (hp : 1 ≤ p) (hf : ∀ i, 0 ≤ f i) (hg : ∀ i, 0 ≤ g i)
    (hf_sum : Summable fun i => f i ^ p) (hg_sum : Summable fun i => g i ^ p) :
    (Summable fun i => (f i + g i) ^ p) ∧
      (∑' i, (f i + g i) ^ p) ^ (1 / p) ≤ (∑' i, f i ^ p) ^ (1 / p) + (∑' i, g i ^ p) ^ (1 / p) :=
  by
  lift f to ι → ℝ≥0 using hf
  lift g to ι → ℝ≥0 using hg
  norm_cast  at *
  exact NNReal.Lp_add_le_tsum hp hf_sum hg_sum
#align real.Lp_add_le_tsum_of_nonneg Real.Lp_add_le_tsum_of_nonneg

theorem summable_Lp_add_of_nonneg (hp : 1 ≤ p) (hf : ∀ i, 0 ≤ f i) (hg : ∀ i, 0 ≤ g i)
    (hf_sum : Summable fun i => f i ^ p) (hg_sum : Summable fun i => g i ^ p) :
    Summable fun i => (f i + g i) ^ p :=
  (Lp_add_le_tsum_of_nonneg hp hf hg hf_sum hg_sum).1
#align real.summable_Lp_add_of_nonneg Real.summable_Lp_add_of_nonneg

theorem Lp_add_le_tsum_of_nonneg' (hp : 1 ≤ p) (hf : ∀ i, 0 ≤ f i) (hg : ∀ i, 0 ≤ g i)
    (hf_sum : Summable fun i => f i ^ p) (hg_sum : Summable fun i => g i ^ p) :
    (∑' i, (f i + g i) ^ p) ^ (1 / p) ≤ (∑' i, f i ^ p) ^ (1 / p) + (∑' i, g i ^ p) ^ (1 / p) :=
  (Lp_add_le_tsum_of_nonneg hp hf hg hf_sum hg_sum).2
#align real.Lp_add_le_tsum_of_nonneg' Real.Lp_add_le_tsum_of_nonneg'

/-- Minkowski inequality: the `L_p` seminorm of the infinite sum of two vectors is less than or
equal to the infinite sum of the `L_p`-seminorms of the summands, if these infinite sums both
exist. A version for `ℝ`-valued functions. For an alternative version, convenient if the infinite
sums are not already expressed as `p`-th powers, see `Lp_add_le_tsum_of_nonneg`. -/
theorem Lp_add_le_hasSum_of_nonneg (hp : 1 ≤ p) (hf : ∀ i, 0 ≤ f i) (hg : ∀ i, 0 ≤ g i) {A B : ℝ}
    (hA : 0 ≤ A) (hB : 0 ≤ B) (hfA : HasSum (fun i => f i ^ p) (A ^ p))
    (hgB : HasSum (fun i => g i ^ p) (B ^ p)) :
    ∃ C, 0 ≤ C ∧ C ≤ A + B ∧ HasSum (fun i => (f i + g i) ^ p) (C ^ p) :=
  by
  lift f to ι → ℝ≥0 using hf
  lift g to ι → ℝ≥0 using hg
  lift A to ℝ≥0 using hA
  lift B to ℝ≥0 using hB
  norm_cast  at hfA hgB
  obtain ⟨C, hC₁, hC₂⟩ := NNReal.Lp_add_le_hasSum hp hfA hgB
  use C
  norm_cast
  exact ⟨zero_le _, hC₁, hC₂⟩
#align real.Lp_add_le_has_sum_of_nonneg Real.Lp_add_le_hasSum_of_nonneg

end Real

namespace ENNReal

variable (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.IsConjugateExponent q) :
    (∑ i in s, f i * g i) ≤ (∑ i in s, f i ^ p) ^ (1 / p) * (∑ i in s, g i ^ q) ^ (1 / q) :=
  by
  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
    ·
      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 := fun 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 ≠ ⊤
  ·
    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] using H'
  have :=
    ENNReal.coe_le_coe.2
      (@NNReal.inner_le_Lp_mul_Lq _ s (fun i => ENNReal.toNNReal (f i))
        (fun i => ENNReal.toNNReal (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 fun i hi => _
      simp [H'.1 i hi, H'.2 i hi, -WithZero.coe_mul, with_top.coe_mul.symm]
#align ennreal.inner_le_Lp_mul_Lq ENNReal.inner_le_Lp_mul_Lq

/-- For `1 ≤ p`, the `p`-th power of the sum of `f i` is bounded above by a constant times the
sum of the `p`-th powers of `f i`. Version for sums over finite sets, with `ℝ≥0∞`-valued functions.
-/
theorem rpow_sum_le_const_mul_sum_rpow (hp : 1 ≤ p) :
    (∑ i in s, f i) ^ p ≤ card s ^ (p - 1) * ∑ i in s, f i ^ p :=
  by
  cases' eq_or_lt_of_le hp with hp hp
  · simp [← hp]
  let q : ℝ := p / (p - 1)
  have hpq : p.is_conjugate_exponent q := by rw [Real.isConjugateExponent_iff hp]
  have hp₁ : 1 / p * p = 1 := one_div_mul_cancel hpq.ne_zero
  have hq : 1 / q * p = p - 1 := by
    rw [← hpq.div_conj_eq_sub_one]
    ring
  simpa only [ENNReal.mul_rpow_of_nonneg _ _ hpq.nonneg, ← ENNReal.rpow_mul, hp₁, hq, coe_one,
    one_mul, one_rpow, rpow_one, Pi.one_apply, sum_const, Nat.smul_one_eq_coe] using
    ENNReal.rpow_le_rpow (inner_le_Lp_mul_Lq s 1 f hpq.symm) hpq.nonneg
#align ennreal.rpow_sum_le_const_mul_sum_rpow ENNReal.rpow_sum_le_const_mul_sum_rpow

/-- 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) :=
  by
  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 ≠ ⊤
  · simpa [ENNReal.rpow_eq_top_iff, asymm Pos, Pos, ENNReal.sum_eq_top_iff, not_or] using H'
  have :=
    ENNReal.coe_le_coe.2
      (@NNReal.Lp_add_le _ s (fun i => ENNReal.toNNReal (f i)) (fun i => ENNReal.toNNReal (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 fun i hi => _
      simp [H'.1 i hi, H'.2 i hi]
#align ennreal.Lp_add_le ENNReal.Lp_add_le

end ENNReal

end HolderMinkowski