/-
Copyright (c) 2020 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn
Ported by: Anatole Dedecker

! This file was ported from Lean 3 source module data.set.accumulate
! leanprover-community/mathlib commit 207cfac9fcd06138865b5d04f7091e46d9320432
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Set.Lattice

/-!
# Accumulate

The function `Accumulate` takes a set `s` and returns `⋃ y ≤ x, s y`.
-/

variable {α β γ : Type _} {s : α → Set β} {t : α → Set γ}

namespace Set

/-- `Accumulate s` is the union of `s y` for `y ≤ x`. -/
def Accumulate [LE α] (s : α → Set β) (x : α) : Set β :=
  ⋃ y ≤ x, s y
#align set.accumulate Set.Accumulate

theorem accumulate_def [LE α] {x : α} : Accumulate s x = ⋃ y ≤ x, s y :=
  rfl
#align set.accumulate_def Set.accumulate_def

@[simp]
theorem mem_accumulate [LE α] {x : α} {z : β} : z ∈ Accumulate s x ↔ ∃ y ≤ x, z ∈ s y := by
  simp_rw [accumulate_def, mem_unionᵢ₂, exists_prop]
#align set.mem_accumulate Set.mem_accumulate

theorem subset_accumulate [Preorder α] {x : α} : s x ⊆ Accumulate s x := fun _ => mem_bunionᵢ le_rfl
#align set.subset_accumulate Set.subset_accumulate

theorem monotone_accumulate [Preorder α] : Monotone (Accumulate s) := fun _ _ hxy =>
  bunionᵢ_subset_bunionᵢ_left fun _ hz => le_trans hz hxy
#align set.monotone_accumulate Set.monotone_accumulate

theorem bunionᵢ_accumulate [Preorder α] (x : α) : (⋃ y ≤ x, Accumulate s y) = ⋃ y ≤ x, s y := by
  apply Subset.antisymm
  · exact unionᵢ₂_subset fun y hy => monotone_accumulate hy
  · exact unionᵢ₂_mono fun y _ => subset_accumulate
#align set.bUnion_accumulate Set.bunionᵢ_accumulate

theorem unionᵢ_accumulate [Preorder α] : (⋃ x, Accumulate s x) = ⋃ x, s x := by
  apply Subset.antisymm
  · simp only [subset_def, mem_unionᵢ, exists_imp, mem_accumulate]
    intro z x x' ⟨_, hz⟩
    exact ⟨x', hz⟩
  · exact unionᵢ_mono fun i => subset_accumulate
#align set.Union_accumulate Set.unionᵢ_accumulate

end Set


import algebra.group_power

theorem nat.cast_pow {α : Type*} [semiring α] (n : ℕ) : ∀ m : ℕ, (n : α) ^ m = (n ^ m : ℕ)
| 0 := nat.cast_one.symm
| (d+1) := show ↑n * ↑n ^ d = ↑(n ^ d * n), by rw [nat.cast_pow d,mul_comm,nat.cast_mul]

theorem int.cast_pow {α : Type*} [ring α] (n : ℤ) : ∀ m : ℕ, (n : α) ^ m = (n ^ m : ℤ)
| 0 := nat.cast_one.symm
| (d+1) := show ↑n * ↑n ^ d = ↑(n * n ^ d), by rw [int.cast_pow d,int.cast_mul]

theorem nat.cast_pow' (n : ℕ) : ∀ m : ℕ, (n : ℤ) ^ m = (n ^ m : ℕ)
| 0 := nat.cast_one.symm
| (d+1) := show ↑n * ↑n ^ d = ↑(n ^ d * n),
by rw [nat.cast_pow',mul_comm,int.coe_nat_mul]

theorem nat.pow_pow (n a : ℕ) : ∀ b : ℕ, (n ^ a) ^ b = n ^ (a * b)
| 0 := by rw mul_zero;refl
| (d+1) := show (n^a)^d * n^a = _, by rw [mul_add,mul_one,nat.pow_add,nat.pow_pow]

theorem nat.mul_div {a b : ℕ} (Ha : a ≠ 0) (Hd :  a ∣ b) : a * (b / a) = b :=
by rw nat.dvd_iff_mod_eq_zero at Hd;exact zero_add (a * (b / a)) ▸ (Hd ▸ nat.mod_add_div b a)

theorem nat.pow_two (a : ℕ) : a ^ 2 = a * a := show (1 * a) * a = _, by rw one_mul


universe u

example {α : Type u} [ring α] (a b c : α) : a * 0 + 0 * b + c * 0 + 0 * a = 0 :=
begin
  rw [mul_zero, mul_zero, zero_mul, zero_mul],
  repeat { rw add_zero }
end

example {α : Type u} [group α] {a b : α} (h : a * b = 1) : a⁻¹ = b :=
  by rw [←(mul_one a⁻¹), ←h, inv_mul_cancel_left]


import data.finset.basic
import data.fintype.basic

namespace finset

theorem ex_x_ne_y_of_card_ge_two {α : Type*} [decidable_eq α]
  {s : finset α} : s.card ≥ 2 → ∃ x ∈ s, ∃ y ∈ s, x ≠ y :=
begin
  intro h_card,
  have hs : ∃ n : ℕ, s.card = n + 2,
  { use s.card - 2,
    rw ← nat.sub_eq_iff_eq_add,
    exact h_card, },
  cases hs with n hn,
  rw finset.card_eq_succ at hn,
  rcases hn with ⟨x, s', hx, hs', hn'⟩,
  rw finset.card_eq_succ at hn',
  rcases hn' with ⟨y, s'', _, hs'', _⟩,
  have hy : y ∈ s',
  { rw ← hs'',
    exact finset.mem_insert_self y s'', },
  use x,
  split,
  { rw ← hs',
    exact finset.mem_insert_self x s', },
  { use y,
    split,
    apply finset.mem_of_subset,
    { rw ← hs',
      exact finset.subset_insert x s', },
    { exact hy, },
    symmetry,
    exact ne_of_mem_of_not_mem hy hx, },
end

end finset

namespace fintype

theorem ex_x_ne_y_of_card_ge_two {α : Type*}
  [decidable_eq α] [fintype α] :
fintype.card α ≥ 2 → ∃ x y : α, x ≠ y :=
begin
  intro h_card,
  rcases finset.ex_x_ne_y_of_card_ge_two
    (show finset.univ.card ≥ 2, by {rw finset.card_univ, exact h_card})
    with ⟨x, _, y, _, hxy⟩,
  exact ⟨x, y, hxy⟩,
end

end fintype


import data.set.basic -- hide
import tactic -- hide
open set -- hide

open set function -- hide

variables {X Y I: Type} -- hide
/-
# Level 1: The image of an indexed union

This level is similar to the previous one. You can use a similar strategy.

-/

/- Lemma
If f is a function and Aᵢ are sets, then f(⋃ Aᵢ) = ⋃ f(Aᵢ)
-/
lemma image_Union (f: X → Y) ( A : I → set X) : f '' ( ⋃ i, A i ) = ⋃ i, f '' A i :=
begin
  ext y,
  split,
  {
    intro h1,
    cases h1 with x hx, -- millor donar noms, per facilitar l'argument
    cases hx with hx1 hx2, -- no tenia bons noms, però almenys són més curts
    simp,
    rw ← hx2, --aquí voldria combinar dues hipotesis per obtenir el goal
    cases hx1 with U hU,
    simp at hU,
    cases hU with hU1 hU2,
    cases hU1 with i hi,
    -- Ara hem de fer servir les hipòtesis que tenim...
    use i, -- l'index que busquem l'acabem d'obtenir al pas anterior
    use x,
    rw hi,
    exact ⟨hU2, rfl⟩,
  },
  {
    intro h1,
    simp at h1,
    cases h1 with i hx,
    simp,
    cases hx with x hx2,
    cases hx2 with hxA hxy,
    use x,
    use i,
  {
    exact hxA,
  },
  {
    exact hxy,
  },
  },
end


import data.nat.basic

namespace nat

lemma max_succ_succ {m n} :
  max (succ m) (succ n) = succ (max m n) :=
begin
  by_cases h1 : m ≤ n,
  rw [max_eq_right h1, max_eq_right (succ_le_succ h1)],
  { rw not_le at h1, have h2 := le_of_lt h1,
    rw [max_eq_left h2, max_eq_left (succ_le_succ h2)] }
end

end nat


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

! This file was ported from Lean 3 source module analysis.sum_integral_comparisons
! leanprover-community/mathlib commit 9003f28797c0664a49e4179487267c494477d853
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.MeasureTheory.Integral.IntervalIntegral
import Mathbin.Data.Set.Function
import Mathbin.Analysis.SpecialFunctions.Integrals

/-!
# Comparing sums and integrals

## Summary

It is often the case that error terms in analysis can be computed by comparing
an infinite sum to the improper integral of an antitone function. This file will eventually enable
that.

At the moment it contains four lemmas in this direction: `antitone_on.integral_le_sum`,
`antitone_on.sum_le_integral` and versions for monotone functions, which can all be paired
with a `filter.tendsto` to estimate some errors.

`TODO`: Add more lemmas to the API to directly address limiting issues

## Main Results

* `antitone_on.integral_le_sum`: The integral of an antitone function is at most the sum of its
  values at integer steps aligning with the left-hand side of the interval
* `antitone_on.sum_le_integral`: The sum of an antitone function along integer steps aligning with
  the right-hand side of the interval is at most the integral of the function along that interval
* `monotone_on.integral_le_sum`: The integral of a monotone function is at most the sum of its
  values at integer steps aligning with the right-hand side of the interval
* `monotone_on.sum_le_integral`: The sum of a monotone function along integer steps aligning with
  the left-hand side of the interval is at most the integral of the function along that interval

## Tags

analysis, comparison, asymptotics
-/

open Set MeasureTheory.MeasureSpace

open BigOperators

variable {x₀ : ℝ} {a b : ℕ} {f : ℝ → ℝ}

theorem AntitoneOn.integral_le_sum (hf : AntitoneOn f (Icc x₀ (x₀ + a))) :
    (∫ x in x₀..x₀ + a, f x) ≤ ∑ i in Finset.range a, f (x₀ + i) :=
  by
  have hint : ∀ k : ℕ, k < a → IntervalIntegrable f volume (x₀ + k) (x₀ + (k + 1 : ℕ)) :=
    by
    intro k hk
    refine' (hf.mono _).IntervalIntegrable
    rw [uIcc_of_le]
    · apply Icc_subset_Icc
      · simp only [le_add_iff_nonneg_right, Nat.cast_nonneg]
      · simp only [add_le_add_iff_left, Nat.cast_le, Nat.succ_le_of_lt hk]
    · simp only [add_le_add_iff_left, Nat.cast_le, Nat.le_succ]
  calc
    (∫ x in x₀..x₀ + a, f x) = ∑ i in Finset.range a, ∫ x in x₀ + i..x₀ + (i + 1 : ℕ), f x :=
      by
      convert(intervalIntegral.sum_integral_adjacent_intervals hint).symm
      simp only [Nat.cast_zero, add_zero]
    _ ≤ ∑ i in Finset.range a, ∫ x in x₀ + i..x₀ + (i + 1 : ℕ), f (x₀ + i) :=
      by
      apply Finset.sum_le_sum fun i hi => _
      have ia : i < a := Finset.mem_range.1 hi
      refine' intervalIntegral.integral_mono_on (by simp) (hint _ ia) (by simp) fun x hx => _
      apply hf _ _ hx.1
      ·
        simp only [ia.le, mem_Icc, le_add_iff_nonneg_right, Nat.cast_nonneg, add_le_add_iff_left,
          Nat.cast_le, and_self_iff]
      · refine' mem_Icc.2 ⟨le_trans (by simp) hx.1, le_trans hx.2 _⟩
        simp only [add_le_add_iff_left, Nat.cast_le, Nat.succ_le_of_lt ia]
    _ = ∑ i in Finset.range a, f (x₀ + i) := by simp

#align antitone_on.integral_le_sum AntitoneOn.integral_le_sum

theorem AntitoneOn.integral_le_sum_Ico (hab : a ≤ b) (hf : AntitoneOn f (Set.Icc a b)) :
    (∫ x in a..b, f x) ≤ ∑ x in Finset.Ico a b, f x :=
  by
  rw [(Nat.sub_add_cancel hab).symm, Nat.cast_add]
  conv =>
    congr
    congr
    skip
    skip
    rw [add_comm]
    skip
    skip
    congr
    congr
    rw [← zero_add a]
  rw [← Finset.sum_Ico_add, Nat.Ico_zero_eq_range]
  conv =>
    rhs
    congr
    skip
    ext
    rw [Nat.cast_add]
  apply AntitoneOn.integral_le_sum
  simp only [hf, hab, Nat.cast_sub, add_sub_cancel'_right]
#align antitone_on.integral_le_sum_Ico AntitoneOn.integral_le_sum_Ico

theorem AntitoneOn.sum_le_integral (hf : AntitoneOn f (Icc x₀ (x₀ + a))) :
    (∑ i in Finset.range a, f (x₀ + (i + 1 : ℕ))) ≤ ∫ x in x₀..x₀ + a, f x :=
  by
  have hint : ∀ k : ℕ, k < a → IntervalIntegrable f volume (x₀ + k) (x₀ + (k + 1 : ℕ)) :=
    by
    intro k hk
    refine' (hf.mono _).IntervalIntegrable
    rw [uIcc_of_le]
    · apply Icc_subset_Icc
      · simp only [le_add_iff_nonneg_right, Nat.cast_nonneg]
      · simp only [add_le_add_iff_left, Nat.cast_le, Nat.succ_le_of_lt hk]
    · simp only [add_le_add_iff_left, Nat.cast_le, Nat.le_succ]
  calc
    (∑ i in Finset.range a, f (x₀ + (i + 1 : ℕ))) =
        ∑ i in Finset.range a, ∫ x in x₀ + i..x₀ + (i + 1 : ℕ), f (x₀ + (i + 1 : ℕ)) :=
      by simp
    _ ≤ ∑ i in Finset.range a, ∫ x in x₀ + i..x₀ + (i + 1 : ℕ), f x :=
      by
      apply Finset.sum_le_sum fun i hi => _
      have ia : i + 1 ≤ a := Finset.mem_range.1 hi
      refine' intervalIntegral.integral_mono_on (by simp) (by simp) (hint _ ia) fun x hx => _
      apply hf _ _ hx.2
      · refine'
          mem_Icc.2
            ⟨le_trans ((le_add_iff_nonneg_right _).2 (Nat.cast_nonneg _)) hx.1, le_trans hx.2 _⟩
        simp only [Nat.cast_le, add_le_add_iff_left, ia]
      · refine' mem_Icc.2 ⟨(le_add_iff_nonneg_right _).2 (Nat.cast_nonneg _), _⟩
        simp only [add_le_add_iff_left, Nat.cast_le, ia]
    _ = ∫ x in x₀..x₀ + a, f x :=
      by
      convert intervalIntegral.sum_integral_adjacent_intervals hint
      simp only [Nat.cast_zero, add_zero]

#align antitone_on.sum_le_integral AntitoneOn.sum_le_integral

theorem AntitoneOn.sum_le_integral_Ico (hab : a ≤ b) (hf : AntitoneOn f (Set.Icc a b)) :
    (∑ i in Finset.Ico a b, f (i + 1 : ℕ)) ≤ ∫ x in a..b, f x :=
  by
  rw [(Nat.sub_add_cancel hab).symm, Nat.cast_add]
  conv =>
    congr
    congr
    congr
    rw [← zero_add a]
    skip
    skip
    skip
    rw [add_comm]
  rw [← Finset.sum_Ico_add, Nat.Ico_zero_eq_range]
  conv =>
    lhs
    congr
    congr
    skip
    ext
    rw [add_assoc, Nat.cast_add]
  apply AntitoneOn.sum_le_integral
  simp only [hf, hab, Nat.cast_sub, add_sub_cancel'_right]
#align antitone_on.sum_le_integral_Ico AntitoneOn.sum_le_integral_Ico

theorem MonotoneOn.sum_le_integral (hf : MonotoneOn f (Icc x₀ (x₀ + a))) :
    (∑ i in Finset.range a, f (x₀ + i)) ≤ ∫ x in x₀..x₀ + a, f x :=
  by
  rw [← neg_le_neg_iff, ← Finset.sum_neg_distrib, ← intervalIntegral.integral_neg]
  exact hf.neg.integral_le_sum
#align monotone_on.sum_le_integral MonotoneOn.sum_le_integral

theorem MonotoneOn.sum_le_integral_Ico (hab : a ≤ b) (hf : MonotoneOn f (Set.Icc a b)) :
    (∑ x in Finset.Ico a b, f x) ≤ ∫ x in a..b, f x :=
  by
  rw [← neg_le_neg_iff, ← Finset.sum_neg_distrib, ← intervalIntegral.integral_neg]
  exact hf.neg.integral_le_sum_Ico hab
#align monotone_on.sum_le_integral_Ico MonotoneOn.sum_le_integral_Ico

theorem MonotoneOn.integral_le_sum (hf : MonotoneOn f (Icc x₀ (x₀ + a))) :
    (∫ x in x₀..x₀ + a, f x) ≤ ∑ i in Finset.range a, f (x₀ + (i + 1 : ℕ)) :=
  by
  rw [← neg_le_neg_iff, ← Finset.sum_neg_distrib, ← intervalIntegral.integral_neg]
  exact hf.neg.sum_le_integral
#align monotone_on.integral_le_sum MonotoneOn.integral_le_sum

theorem MonotoneOn.integral_le_sum_Ico (hab : a ≤ b) (hf : MonotoneOn f (Set.Icc a b)) :
    (∫ x in a..b, f x) ≤ ∑ i in Finset.Ico a b, f (i + 1 : ℕ) :=
  by
  rw [← neg_le_neg_iff, ← Finset.sum_neg_distrib, ← intervalIntegral.integral_neg]
  exact hf.neg.sum_le_integral_Ico hab
#align monotone_on.integral_le_sum_Ico MonotoneOn.integral_le_sum_Ico


/-
Copyright (c) 2021 Anne Baanen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anne Baanen
-/
import data.fin.interval

/-!
# The structure of `fintype (fin n)`

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

This file contains some basic results about the `fintype` instance for `fin`,
especially properties of `finset.univ : finset (fin n)`.
-/

open finset
open fintype

namespace fin

variables {α β : Type*} {n : ℕ}

-- TODO: replace `subtype` with `coe` in the name of this lemma and `fin.map_subtype_embedding_Iio`
lemma map_subtype_embedding_univ :
  (finset.univ : finset (fin n)).map fin.coe_embedding = Iio n :=
begin
  ext,
  simp [order_iso_subtype.symm.surjective.exists, order_iso.symm],
end

@[simp] lemma Ioi_zero_eq_map :
  Ioi (0 : fin n.succ) = univ.map (fin.succ_embedding _).to_embedding :=
begin
  ext i,
  simp only [mem_Ioi, mem_map, mem_univ, function.embedding.coe_fn_mk, exists_true_left],
  split,
  { refine cases _ _ i,
    { rintro ⟨⟨⟩⟩ },
    { intros j _, exact ⟨j, rfl⟩ } },
  { rintro ⟨i, _, rfl⟩,
    exact succ_pos _ },
end

@[simp] lemma Iio_last_eq_map :
  Iio (fin.last n) = finset.univ.map fin.cast_succ.to_embedding :=
begin
  apply finset.map_injective fin.coe_embedding,
  rw [finset.map_map, fin.map_subtype_embedding_Iio, fin.coe_last],
  exact map_subtype_embedding_univ.symm
end

@[simp] lemma Ioi_succ (i : fin n) :
  Ioi i.succ = (Ioi i).map (fin.succ_embedding _).to_embedding :=
begin
  ext i,
  simp only [mem_filter, mem_Ioi, mem_map, mem_univ, true_and,
  function.embedding.coe_fn_mk, exists_true_left],
  split,
  { refine cases _ _ i,
    { rintro ⟨⟨⟩⟩ },
    { intros i hi,
      refine ⟨i, succ_lt_succ_iff.mp hi, rfl⟩ } },
  { rintro ⟨i, hi, rfl⟩, simpa },
end

@[simp] lemma Iio_cast_succ (i : fin n) :
  Iio (cast_succ i) = (Iio i).map fin.cast_succ.to_embedding :=
begin
  apply finset.map_injective fin.coe_embedding,
  rw [finset.map_map, fin.map_subtype_embedding_Iio],
  exact (fin.map_subtype_embedding_Iio i).symm,
end

lemma card_filter_univ_succ' (p : fin (n + 1) → Prop) [decidable_pred p] :
  (univ.filter p).card = (ite (p 0) 1 0) + (univ.filter (p ∘ fin.succ)).card :=
begin
  rw [fin.univ_succ, filter_cons, card_disj_union, filter_map, card_map],
  split_ifs; simp,
end

lemma card_filter_univ_succ (p : fin (n + 1) → Prop) [decidable_pred p] :
  (univ.filter p).card =
    if p 0 then (univ.filter (p ∘ fin.succ)).card + 1 else (univ.filter (p ∘ fin.succ)).card :=
(card_filter_univ_succ' p).trans (by split_ifs; simp [add_comm 1])

lemma card_filter_univ_eq_vector_nth_eq_count [decidable_eq α] (a : α) (v : vector α n) :
  (univ.filter $ λ i, a = v.nth i).card = v.to_list.count a :=
begin
  induction v using vector.induction_on with n x xs hxs,
  { simp },
  { simp_rw [card_filter_univ_succ', vector.nth_cons_zero, vector.to_list_cons,
      function.comp, vector.nth_cons_succ, hxs, list.count_cons', add_comm (ite (a = x) 1 0)] }
end

end fin


import ..prooflab
import lectures.lec6_proposition
import data.real.basic
import data.complex.exponential

/-! # Homework 4: ...
Homework must be done individually.
Replace the placeholders (e.g., `:= sorry`) with your solutions.

You are allowed to use all the tactics we have learned so far.

Pro Tip: Don't forget you know the tactics `linarith` and `ring`!
 -/

namespace PROOFS

variables {P Q R : Prop}

/-! ## Question 1 (20 points):
Give a proof of the proposition `(P ∧ R) → (R → P → Q) → Q`.
-/

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

/-! ## Question 2 (20 points):
Prove the implication
`m * n - m - n + 1 = 1 → (m = 2) ∧ (n = 2)`
for natural numbers `m` and `n` by filling in `sorry` placeholders below.

You need to use the lemma `mul_eq_one_of_pos_of_pos` in below which says if the multiplication of two positive integers is `1` then both of them must be `1`. Notice that we did not provide the proof of this lemma here, and __you don't need to provide it either__. We will construct a proof of this lemma in the next lecture.
-/

-- no need to solve the following, just use it in the next proof
lemma mul_eq_one_of_pos_of_pos  (m n : ℤ) (h : m * n = 1) :
  (0 < m ∧ 0 < n) → (m = 1 ∧ n = 1) :=
begin
  sorry,
end

-- give a proof of this one using the lemma above.
example (m n : ℤ) :
  m * n - m - n + 1 = 1 → (1 < m ∧ 1 < n) → (m = 2) ∧ (n = 2) :=
begin
  sorry,
end

/-! ## Question 3 (20 points):
Construct a proof of the proposition `abs (2 * x - 1) < 5 → -2 < x `. You are allowed to use the lemma `abs_lt`.
-/

section
variables a b : ℝ
#check (abs_lt : |a| < b ↔ -b < a ∧ a < b)

end
example (x y : ℝ) : abs (2 * x - 1) < 5 → -2 < x :=
begin
  sorry,
end

/-! ## Question 4 (20 points):
For `x : ℝ`, the term `real.exp x` encodes the exponential e^x.
-/

section
variables a b c : ℝ
#check (real.exp_le_exp.mpr : a ≤ b → real.exp a ≤ real.exp b)
end

example (a b c : ℝ) (h : 1 ≤ a ∧ b ≤ c) :
  2 + a + real.exp b ≤ 3 * a + real.exp c :=
begin
  sorry,
end

/-! ## Question 5 (20 points):
Prove the following statement. You might like to use any of the following lemmas (most likely you need only some of them not all.):
- `abs_mul`
- `abs_lt`.
- `abs_nonneg`
- `abs_pos`
- `real_le_of_mul_nonneg_left`
- `real_lt_of_mul_pos_right`
- `real_le_mul_right`
-/

section
variables a b : ℝ
#check (abs_mul a b : |a * b| = |a| * |b|)
#check (abs_lt : |a| < b ↔ -b < a ∧ a < b)
end

lemma real_le_of_mul_nonneg_left {a b c : ℝ} (h₀ : a < b) (h₁ : 0 ≤ c):
  c * a ≤ c * b :=
begin
  apply mul_le_mul_of_nonneg_left,
  {
    apply le_of_lt,
    apply h₀,
  },
  {
     apply h₁,
  },
end

lemma real_lt_of_mul_pos_right {a b c : ℝ} (h₀ : a < b) (h₁ : 0 < c):
  a * c < b * c :=
begin
  rw mul_comm a c,
  rw mul_comm b c,
  apply mul_lt_mul_of_pos_left,
  assumption',
end

lemma real_le_mul_right {a b c : ℝ} (h₀ : a ≤ b) (h₁ : 0 < c):
  a * c ≤ b * c :=
begin
  apply (mul_le_mul_right h₁).mpr,
  assumption,
end

#check abs_nonneg

example (x y ε : ℝ) :
  (0 < ε ∧ ε ≤ 1) → (abs x < ε ∧ abs y < ε) → abs (x * y) < ε :=
begin
  sorry,
end

end PROOFS


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

import ring_theory.witt_vector.is_poly

/-!
## Multiplication by `n` in the ring of Witt vectors

In this file we show that multiplication by `n` in the ring of Witt vectors
is a polynomial function. We then use this fact to show that the composition of Frobenius
and Verschiebung is equal to multiplication by `p`.

### Main declarations

* `mul_n_is_poly`: multiplication by `n` is a polynomial function

## References

* [Hazewinkel, *Witt Vectors*][Haze09]

* [Commelin and Lewis, *Formalizing the Ring of Witt Vectors*][CL21]
-/

namespace witt_vector

variables {p : ℕ} {R : Type*} [hp : fact p.prime] [comm_ring R]
local notation `𝕎` := witt_vector p -- type as `\bbW`

open mv_polynomial
noncomputable theory

include hp

variable (p)

/-- `witt_mul_n p n` is the family of polynomials that computes
the coefficients of `x * n` in terms of the coefficients of the Witt vector `x`. -/
noncomputable
def witt_mul_n : ℕ → ℕ → mv_polynomial ℕ ℤ
| 0     := 0
| (n+1) := λ k, bind₁ (function.uncurry $ ![(witt_mul_n n), X]) (witt_add p k)

variable {p}

lemma mul_n_coeff (n : ℕ) (x : 𝕎 R) (k : ℕ) :
  (x * n).coeff k = aeval x.coeff (witt_mul_n p n k) :=
begin
  induction n with n ih generalizing k,
  { simp only [nat.nat_zero_eq_zero, nat.cast_zero, mul_zero,
      zero_coeff, witt_mul_n, alg_hom.map_zero, pi.zero_apply], },
  { rw [witt_mul_n, nat.succ_eq_add_one, nat.cast_add, nat.cast_one, mul_add, mul_one,
      aeval_bind₁, add_coeff],
    apply eval₂_hom_congr (ring_hom.ext_int _ _) _ rfl,
    ext1 ⟨b, i⟩,
    fin_cases b,
    { simp only [function.uncurry, matrix.cons_val_zero, ih] },
    { simp only [function.uncurry, matrix.cons_val_one, matrix.head_cons, aeval_X] } }
end

variables (p)

/-- Multiplication by `n` is a polynomial function. -/
@[is_poly] lemma mul_n_is_poly (n : ℕ) : is_poly p (λ R _Rcr x, by exactI x * n) :=
⟨⟨witt_mul_n p n, λ R _Rcr x, by { funext k, exactI mul_n_coeff n x k }⟩⟩

@[simp] lemma bind₁_witt_mul_n_witt_polynomial (n k : ℕ) :
  bind₁ (witt_mul_n p n) (witt_polynomial p ℤ k) = n * witt_polynomial p ℤ k :=
begin
  induction n with n ih,
  { simp only [witt_mul_n, nat.cast_zero, zero_mul, bind₁_zero_witt_polynomial] },
  { rw [witt_mul_n, ← bind₁_bind₁, witt_add, witt_structure_int_prop],
    simp only [alg_hom.map_add, nat.cast_succ, bind₁_X_right],
    rw [add_mul, one_mul, bind₁_rename, bind₁_rename],
    simp only [ih, function.uncurry, function.comp, bind₁_X_left, alg_hom.id_apply,
      matrix.cons_val_zero, matrix.head_cons, matrix.cons_val_one], }
end

end witt_vector


/-
Copyright (c) 2021 Heather Macbeth. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Heather Macbeth
-/
import analysis.mean_inequalities
import analysis.mean_inequalities_pow
import analysis.normed.group.pointwise
import topology.algebra.order.liminf_limsup

/-!
# ℓp space

This file describes properties of elements `f` of a pi-type `Π i, E i` with finite "norm",
defined for `p:ℝ≥0∞` as the size of the support of `f` if `p=0`, `(∑' a, ∥f a∥^p) ^ (1/p)` for
`0 < p < ∞` and `⨆ a, ∥f a∥` for `p=∞`.

The Prop-valued `mem_ℓp f p` states that a function `f : Π i, E i` has finite norm according
to the above definition; that is, `f` has finite support if `p = 0`, `summable (λ a, ∥f a∥^p)` if
`0 < p < ∞`, and `bdd_above (norm '' (set.range f))` if `p = ∞`.

The space `lp E p` is the subtype of elements of `Π i : α, E i` which satisfy `mem_ℓp f p`. For
`1 ≤ p`, the "norm" is genuinely a norm and `lp` is a complete metric space.

## Main definitions

* `mem_ℓp f p` : property that the function `f` satisfies, as appropriate, `f` finitely supported
  if `p = 0`, `summable (λ a, ∥f a∥^p)` if `0 < p < ∞`, and `bdd_above (norm '' (set.range f))` if
  `p = ∞`.
* `lp E p` : elements of `Π i : α, E i` such that `mem_ℓp f p`. Defined as an `add_subgroup` of
  a type synonym `pre_lp` for `Π i : α, E i`, and equipped with a `normed_group` structure.
  Under appropriate conditions, this is also equipped with the instances `lp.normed_space`,
  `lp.complete_space`, and `lp.normed_ring`.

## Main results

* `mem_ℓp.of_exponent_ge`: For `q ≤ p`, a function which is `mem_ℓp` for `q` is also `mem_ℓp` for
  `p`
* `lp.mem_ℓp_of_tendsto`, `lp.norm_le_of_tendsto`: A pointwise limit of functions in `lp`, all with
  `lp` norm `≤ C`, is itself in `lp` and has `lp` norm `≤ C`.
* `lp.tsum_mul_le_mul_norm`: basic form of Hölder's inequality

## Implementation

Since `lp` is defined as an `add_subgroup`, dot notation does not work. Use `lp.norm_neg f` to
say that `∥-f∥ = ∥f∥`, instead of the non-working `f.norm_neg`.

## TODO

* More versions of Hölder's inequality (for example: the case `p = 1`, `q = ∞`; a version for normed
  rings which has `∥∑' i, f i * g i∥` rather than `∑' i, ∥f i∥ * g i∥` on the RHS; a version for
  three exponents satisfying `1 / r = 1 / p + 1 / q`)
* Equivalence with `pi_Lp`, for `α` finite
* Equivalence with `measure_theory.Lp`, for `f : α → E` (i.e., functions rather than pi-types) and
  the counting measure on `α`
* Equivalence with `bounded_continuous_function`, for `f : α → E` (i.e., functions rather than
  pi-types) and `p = ∞`, and the discrete topology on `α`

-/

noncomputable theory
open_locale nnreal ennreal big_operators

variables {α : Type*} {E : α → Type*} {p q : ℝ≥0∞} [Π i, normed_group (E i)]

/-!
### `mem_ℓp` predicate

-/

/-- The property that `f : Π i : α, E i`
* is finitely supported, if `p = 0`, or
* admits an upper bound for `set.range (λ i, ∥f i∥)`, if `p = ∞`, or
* has the series `∑' i, ∥f i∥ ^ p` be summable, if `0 < p < ∞`. -/
def mem_ℓp (f : Π i, E i) (p : ℝ≥0∞) : Prop :=
if p = 0 then (set.finite {i | f i ≠ 0}) else
  (if p = ∞ then bdd_above (set.range (λ i, ∥f i∥)) else summable (λ i, ∥f i∥ ^ p.to_real))

lemma mem_ℓp_zero_iff {f : Π i, E i} : mem_ℓp f 0 ↔ set.finite {i | f i ≠ 0} :=
by dsimp [mem_ℓp]; rw [if_pos rfl]

lemma mem_ℓp_zero {f : Π i, E i} (hf : set.finite {i | f i ≠ 0}) : mem_ℓp f 0 :=
mem_ℓp_zero_iff.2 hf

lemma mem_ℓp_infty_iff {f : Π i, E i} : mem_ℓp f ∞ ↔ bdd_above (set.range (λ i, ∥f i∥)) :=
by dsimp [mem_ℓp]; rw [if_neg ennreal.top_ne_zero, if_pos rfl]

lemma mem_ℓp_infty {f : Π i, E i} (hf : bdd_above (set.range (λ i, ∥f i∥))) : mem_ℓp f ∞ :=
mem_ℓp_infty_iff.2 hf

lemma mem_ℓp_gen_iff (hp : 0 < p.to_real) {f : Π i, E i} :
  mem_ℓp f p ↔ summable (λ i, ∥f i∥ ^ p.to_real) :=
begin
  rw ennreal.to_real_pos_iff at hp,
  dsimp [mem_ℓp],
  rw [if_neg hp.1.ne', if_neg hp.2.ne],
end

lemma mem_ℓp_gen {f : Π i, E i} (hf : summable (λ i, ∥f i∥ ^ p.to_real)) :
  mem_ℓp f p :=
begin
  rcases p.trichotomy with rfl | rfl | hp,
  { apply mem_ℓp_zero,
    have H : summable (λ i : α, (1:ℝ)) := by simpa using hf,
    exact (finite_of_summable_const (by norm_num) H).subset (set.subset_univ _) },
  { apply mem_ℓp_infty,
    have H : summable (λ i : α, (1:ℝ)) := by simpa using hf,
    simpa using ((finite_of_summable_const (by norm_num) H).image (λ i, ∥f i∥)).bdd_above },
  exact (mem_ℓp_gen_iff hp).2 hf
end

lemma mem_ℓp_gen' {C : ℝ} {f : Π i, E i} (hf : ∀ s : finset α, ∑ i in s, ∥f i∥ ^ p.to_real ≤ C) :
  mem_ℓp f p :=
begin
  apply mem_ℓp_gen,
  use ⨆ s : finset α, ∑ i in s, ∥f i∥ ^ p.to_real,
  apply has_sum_of_is_lub_of_nonneg,
  { intros b,
    exact real.rpow_nonneg_of_nonneg (norm_nonneg _) _ },
  apply is_lub_csupr,
  use C,
  rintros - ⟨s, rfl⟩,
  exact hf s
end

lemma zero_mem_ℓp : mem_ℓp (0 : Π i, E i) p :=
begin
  rcases p.trichotomy with rfl | rfl | hp,
  { apply mem_ℓp_zero,
    simp },
  { apply mem_ℓp_infty,
    simp only [norm_zero, pi.zero_apply],
    exact bdd_above_singleton.mono set.range_const_subset, },
  { apply mem_ℓp_gen,
    simp [real.zero_rpow hp.ne', summable_zero], }
end

lemma zero_mem_ℓp' : mem_ℓp (λ i : α, (0 : E i)) p := zero_mem_ℓp

namespace mem_ℓp

lemma finite_dsupport {f : Π i, E i} (hf : mem_ℓp f 0) : set.finite {i | f i ≠ 0} :=
mem_ℓp_zero_iff.1 hf

lemma bdd_above {f : Π i, E i} (hf : mem_ℓp f ∞) : bdd_above (set.range (λ i, ∥f i∥)) :=
mem_ℓp_infty_iff.1 hf

lemma summable (hp : 0 < p.to_real) {f : Π i, E i} (hf : mem_ℓp f p) :
  summable (λ i, ∥f i∥ ^ p.to_real) :=
(mem_ℓp_gen_iff hp).1 hf

lemma neg {f : Π i, E i} (hf : mem_ℓp f p) : mem_ℓp (-f) p :=
begin
  rcases p.trichotomy with rfl | rfl | hp,
  { apply mem_ℓp_zero,
    simp [hf.finite_dsupport] },
  { apply mem_ℓp_infty,
    simpa using hf.bdd_above },
  { apply mem_ℓp_gen,
    simpa using hf.summable hp },
end

@[simp] lemma neg_iff {f : Π i, E i} : mem_ℓp (-f) p ↔ mem_ℓp f p :=
⟨λ h, neg_neg f ▸ h.neg, mem_ℓp.neg⟩

lemma of_exponent_ge {p q : ℝ≥0∞} {f : Π i, E i}
  (hfq : mem_ℓp f q) (hpq : q ≤ p) :
  mem_ℓp f p :=
begin
  rcases ennreal.trichotomy₂ hpq with ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ | ⟨rfl, hp⟩ | ⟨rfl, rfl⟩ | ⟨hq, rfl⟩
    | ⟨hq, hp, hpq'⟩,
  { exact hfq },
  { apply mem_ℓp_infty,
    obtain ⟨C, hC⟩ := (hfq.finite_dsupport.image (λ i, ∥f i∥)).bdd_above,
    use max 0 C,
    rintros x ⟨i, rfl⟩,
    by_cases hi : f i = 0,
    { simp [hi] },
    { exact (hC ⟨i, hi, rfl⟩).trans (le_max_right _ _) } },
  { apply mem_ℓp_gen,
    have : ∀ i ∉ hfq.finite_dsupport.to_finset, ∥f i∥ ^ p.to_real = 0,
    { intros i hi,
      have : f i = 0 := by simpa using hi,
      simp [this, real.zero_rpow hp.ne'] },
    exact summable_of_ne_finset_zero this },
  { exact hfq },
  { apply mem_ℓp_infty,
    obtain ⟨A, hA⟩ := (hfq.summable hq).tendsto_cofinite_zero.bdd_above_range_of_cofinite,
    use A ^ (q.to_real⁻¹),
    rintros x ⟨i, rfl⟩,
    have : 0 ≤ ∥f i∥ ^ q.to_real := real.rpow_nonneg_of_nonneg (norm_nonneg _) _,
    simpa [← real.rpow_mul, mul_inv_cancel hq.ne'] using
      real.rpow_le_rpow this (hA ⟨i, rfl⟩) (inv_nonneg.mpr hq.le) },
  { apply mem_ℓp_gen,
    have hf' := hfq.summable hq,
    refine summable_of_norm_bounded_eventually _ hf' (@set.finite.subset _ {i | 1 ≤ ∥f i∥} _ _ _),
    { have H : {x : α | 1 ≤ ∥f x∥ ^ q.to_real}.finite,
      { simpa using eventually_lt_of_tendsto_lt (by norm_num : (0:ℝ) < 1)
          hf'.tendsto_cofinite_zero },
      exact H.subset (λ i hi, real.one_le_rpow hi hq.le) },
    { show ∀ i, ¬ (|∥f i∥ ^ p.to_real| ≤ ∥f i∥ ^ q.to_real) → 1 ≤ ∥f i∥,
      intros i hi,
      have : 0 ≤ ∥f i∥ ^ p.to_real := real.rpow_nonneg_of_nonneg (norm_nonneg _) p.to_real,
      simp only [abs_of_nonneg, this] at hi,
      contrapose! hi,
      exact real.rpow_le_rpow_of_exponent_ge' (norm_nonneg _) hi.le hq.le hpq' } }
end

lemma add {f g : Π i, E i} (hf : mem_ℓp f p) (hg : mem_ℓp g p) : mem_ℓp (f + g) p :=
begin
  rcases p.trichotomy with rfl | rfl | hp,
  { apply mem_ℓp_zero,
    refine (hf.finite_dsupport.union hg.finite_dsupport).subset (λ i, _),
    simp only [pi.add_apply, ne.def, set.mem_union_eq, set.mem_set_of_eq],
    contrapose!,
    rintros ⟨hf', hg'⟩,
    simp [hf', hg'] },
  { apply mem_ℓp_infty,
    obtain ⟨A, hA⟩ := hf.bdd_above,
    obtain ⟨B, hB⟩ := hg.bdd_above,
    refine ⟨A + B, _⟩,
    rintros a ⟨i, rfl⟩,
    exact le_trans (norm_add_le _ _) (add_le_add (hA ⟨i, rfl⟩) (hB ⟨i, rfl⟩)) },
  apply mem_ℓp_gen,
  let C : ℝ := if p.to_real < 1 then 1 else 2 ^ (p.to_real - 1),
  refine summable_of_nonneg_of_le _ (λ i, _) (((hf.summable hp).add (hg.summable hp)).mul_left C),
  { exact λ b, real.rpow_nonneg_of_nonneg (norm_nonneg (f b + g b)) p.to_real },
  { refine (real.rpow_le_rpow (norm_nonneg _) (norm_add_le _ _) hp.le).trans _,
    dsimp [C],
    split_ifs with h h,
    { simpa using nnreal.coe_le_coe.2 (nnreal.rpow_add_le_add_rpow (∥f i∥₊) (∥g i∥₊) hp h.le) },
    { let F : fin 2 → ℝ≥0 := ![∥f i∥₊, ∥g i∥₊],
      have : ∀ i, (0:ℝ) ≤ F i := λ i, (F i).coe_nonneg,
      simp only [not_lt] at h,
      simpa [F, fin.sum_univ_succ] using
        real.rpow_sum_le_const_mul_sum_rpow_of_nonneg (finset.univ : finset (fin 2)) h
        (λ i _, (F i).coe_nonneg) } }
end

lemma sub {f g : Π i, E i} (hf : mem_ℓp f p) (hg : mem_ℓp g p) : mem_ℓp (f - g) p :=
by { rw sub_eq_add_neg, exact hf.add hg.neg }

lemma finset_sum {ι} (s : finset ι) {f : ι → Π i, E i} (hf : ∀ i ∈ s, mem_ℓp (f i) p) :
  mem_ℓp (λ a, ∑ i in s, f i a) p :=
begin
  haveI : decidable_eq ι := classical.dec_eq _,
  revert hf,
  refine finset.induction_on s _ _,
  { simp only [zero_mem_ℓp', finset.sum_empty, implies_true_iff], },
  { intros i s his ih hf,
    simp only [his, finset.sum_insert, not_false_iff],
    exact (hf i (s.mem_insert_self i)).add (ih (λ j hj, hf j (finset.mem_insert_of_mem hj))), },
end

section normed_space

variables {𝕜 : Type*} [normed_field 𝕜] [Π i, normed_space 𝕜 (E i)]

lemma const_smul {f : Π i, E i} (hf : mem_ℓp f p) (c : 𝕜) : mem_ℓp (c • f) p :=
begin
  rcases p.trichotomy with rfl | rfl | hp,
  { apply mem_ℓp_zero,
    refine hf.finite_dsupport.subset (λ i, (_ : ¬c • f i = 0 → ¬f i = 0)),
    exact not_imp_not.mpr (λ hf', hf'.symm ▸ (smul_zero c)) },
  { obtain ⟨A, hA⟩ := hf.bdd_above,
    refine mem_ℓp_infty ⟨∥c∥ * A, _⟩,
    rintros a ⟨i, rfl⟩,
    simpa [norm_smul] using mul_le_mul_of_nonneg_left (hA ⟨i, rfl⟩) (norm_nonneg c) },
  { apply mem_ℓp_gen,
    convert (hf.summable hp).mul_left (∥c∥ ^ p.to_real),
    ext i,
    simp [norm_smul, real.mul_rpow (norm_nonneg c) (norm_nonneg (f i))] },
end

lemma const_mul {f : α → 𝕜} (hf : mem_ℓp f p) (c : 𝕜) : mem_ℓp (λ x, c * f x) p :=
@mem_ℓp.const_smul α (λ i, 𝕜) _ _ 𝕜 _ _ _ hf c

end normed_space

end mem_ℓp

/-!
### lp space

The space of elements of `Π i, E i` satisfying the predicate `mem_ℓp`.
-/

/-- We define `pre_lp E` to be a type synonym for `Π i, E i` which, importantly, does not inherit
the `pi` topology on `Π i, E i` (otherwise this topology would descend to `lp E p` and conflict
with the normed group topology we will later equip it with.)

We choose to deal with this issue by making a type synonym for `Π i, E i` rather than for the `lp`
subgroup itself, because this allows all the spaces `lp E p` (for varying `p`) to be subgroups of
the same ambient group, which permits lemma statements like `lp.monotone` (below). -/
@[derive add_comm_group, nolint unused_arguments]
def pre_lp (E : α → Type*) [Π i, normed_group (E i)] : Type* := Π i, E i

instance pre_lp.unique [is_empty α] : unique (pre_lp E) := pi.unique_of_is_empty E

/-- lp space -/
def lp (E : α → Type*) [Π i, normed_group (E i)]
  (p : ℝ≥0∞) : add_subgroup (pre_lp E) :=
{ carrier := {f | mem_ℓp f p},
  zero_mem' := zero_mem_ℓp,
  add_mem' := λ f g, mem_ℓp.add,
  neg_mem' := λ f, mem_ℓp.neg }

namespace lp

instance : has_coe (lp E p) (Π i, E i) := coe_subtype
instance : has_coe_to_fun (lp E p) (λ _, Π i, E i) := ⟨λ f, ((f : Π i, E i) : Π i, E i)⟩

@[ext] lemma ext {f g : lp E p} (h : (f : Π i, E i) = g) : f = g :=
subtype.ext h

protected lemma ext_iff {f g : lp E p} : f = g ↔ (f : Π i, E i) = g :=
subtype.ext_iff

lemma eq_zero' [is_empty α] (f : lp E p) : f = 0 := subsingleton.elim f 0

protected lemma monotone {p q : ℝ≥0∞} (hpq : q ≤ p) : lp E q ≤ lp E p :=
λ f hf, mem_ℓp.of_exponent_ge hf hpq

protected lemma mem_ℓp (f : lp E p) : mem_ℓp f p := f.prop

variables (E p)
@[simp] lemma coe_fn_zero : ⇑(0 : lp E p) = 0 := rfl
variables {E p}

@[simp] lemma coe_fn_neg (f : lp E p) : ⇑(-f) = -f := rfl

@[simp] lemma coe_fn_add (f g : lp E p) : ⇑(f + g) = f + g := rfl

@[simp] lemma coe_fn_sum {ι : Type*} (f : ι → lp E p) (s : finset ι) :
  ⇑(∑ i in s, f i) = ∑ i in s, ⇑(f i) :=
begin
  classical,
  refine finset.induction _ _ s,
  { simp },
  intros i s his,
  simp [finset.sum_insert his],
end

@[simp] lemma coe_fn_sub (f g : lp E p) : ⇑(f - g) = f - g := rfl

instance : has_norm (lp E p) :=
{ norm := λ f, if hp : p = 0 then by subst hp; exact (lp.mem_ℓp f).finite_dsupport.to_finset.card
   else (if p = ∞ then ⨆ i, ∥f i∥ else (∑' i, ∥f i∥ ^ p.to_real) ^ (1/p.to_real)) }

lemma norm_eq_card_dsupport (f : lp E 0) : ∥f∥ = (lp.mem_ℓp f).finite_dsupport.to_finset.card :=
dif_pos rfl

lemma norm_eq_csupr (f : lp E ∞) : ∥f∥ = ⨆ i, ∥f i∥ :=
begin
  dsimp [norm],
  rw [dif_neg ennreal.top_ne_zero, if_pos rfl]
end

lemma is_lub_norm [nonempty α] (f : lp E ∞) : is_lub (set.range (λ i, ∥f i∥)) ∥f∥ :=
begin
  rw lp.norm_eq_csupr,
  exact is_lub_csupr (lp.mem_ℓp f)
end

lemma norm_eq_tsum_rpow (hp : 0 < p.to_real) (f : lp E p) :
  ∥f∥ = (∑' i, ∥f i∥ ^ p.to_real) ^ (1/p.to_real) :=
begin
  dsimp [norm],
  rw ennreal.to_real_pos_iff at hp,
  rw [dif_neg hp.1.ne', if_neg hp.2.ne],
end

lemma norm_rpow_eq_tsum (hp : 0 < p.to_real) (f : lp E p) :
  ∥f∥ ^ p.to_real = ∑' i, ∥f i∥ ^ p.to_real :=
begin
  rw [norm_eq_tsum_rpow hp, ← real.rpow_mul],
  { field_simp [hp.ne'] },
  apply tsum_nonneg,
  intros i,
  calc (0:ℝ) = 0 ^ p.to_real : by rw real.zero_rpow hp.ne'
  ... ≤ _ : real.rpow_le_rpow rfl.le (norm_nonneg (f i)) hp.le
end

lemma has_sum_norm (hp : 0 < p.to_real) (f : lp E p) :
  has_sum (λ i, ∥f i∥ ^ p.to_real) (∥f∥ ^ p.to_real) :=
begin
  rw norm_rpow_eq_tsum hp,
  exact ((lp.mem_ℓp f).summable hp).has_sum
end

lemma norm_nonneg' (f : lp E p) : 0 ≤ ∥f∥ :=
begin
  rcases p.trichotomy with rfl | rfl | hp,
  { simp [lp.norm_eq_card_dsupport f] },
  { cases is_empty_or_nonempty α with _i _i; resetI,
    { rw lp.norm_eq_csupr,
      simp [real.csupr_empty] },
    inhabit α,
    exact (norm_nonneg (f default)).trans ((lp.is_lub_norm f).1 ⟨default, rfl⟩) },
  { rw lp.norm_eq_tsum_rpow hp f,
    refine real.rpow_nonneg_of_nonneg (tsum_nonneg _) _,
    exact λ i, real.rpow_nonneg_of_nonneg (norm_nonneg _) _ },
end

@[simp] lemma norm_zero : ∥(0 : lp E p)∥ = 0 :=
begin
  rcases p.trichotomy with rfl | rfl | hp,
  { simp [lp.norm_eq_card_dsupport] },
  { simp [lp.norm_eq_csupr] },
  { rw lp.norm_eq_tsum_rpow hp,
    have hp' : 1 / p.to_real ≠ 0 := one_div_ne_zero hp.ne',
    simpa [real.zero_rpow hp.ne'] using real.zero_rpow hp' }
end

lemma norm_eq_zero_iff ⦃f : lp E p⦄ : ∥f∥ = 0 ↔ f = 0 :=
begin
  classical,
  refine ⟨λ h, _, by { rintros rfl, exact norm_zero }⟩,
  rcases p.trichotomy with rfl | rfl | hp,
  { ext i,
    have : {i : α | ¬f i = 0} = ∅ := by simpa [lp.norm_eq_card_dsupport f] using h,
    have : (¬ (f i = 0)) = false := congr_fun this i,
    tauto },
  { cases is_empty_or_nonempty α with _i _i; resetI,
    { simp },
    have H : is_lub (set.range (λ i, ∥f i∥)) 0,
    { simpa [h] using lp.is_lub_norm f },
    ext i,
    have : ∥f i∥ = 0 := le_antisymm (H.1 ⟨i, rfl⟩) (norm_nonneg _),
    simpa using this },
  { have hf : has_sum (λ (i : α), ∥f i∥ ^ p.to_real) 0,
    { have := lp.has_sum_norm hp f,
      rwa [h, real.zero_rpow hp.ne'] at this },
    have : ∀ i, 0 ≤ ∥f i∥ ^ p.to_real := λ i, real.rpow_nonneg_of_nonneg (norm_nonneg _) _,
    rw has_sum_zero_iff_of_nonneg this at hf,
    ext i,
    have : f i = 0 ∧ p.to_real ≠ 0,
    { simpa [real.rpow_eq_zero_iff_of_nonneg (norm_nonneg (f i))] using congr_fun hf i },
    exact this.1 },
end

lemma eq_zero_iff_coe_fn_eq_zero {f : lp E p} : f = 0 ↔ ⇑f = 0 :=
by rw [lp.ext_iff, coe_fn_zero]

@[simp] lemma norm_neg ⦃f : lp E p⦄ : ∥-f∥ = ∥f∥ :=
begin
  rcases p.trichotomy with rfl | rfl | hp,
  { simp [lp.norm_eq_card_dsupport] },
  { cases is_empty_or_nonempty α; resetI,
    { simp [lp.eq_zero' f], },
    apply (lp.is_lub_norm (-f)).unique,
    simpa using lp.is_lub_norm f },
  { suffices : ∥-f∥ ^ p.to_real = ∥f∥ ^ p.to_real,
    { exact real.rpow_left_inj_on hp.ne' (norm_nonneg' _) (norm_nonneg' _) this },
    apply (lp.has_sum_norm hp (-f)).unique,
    simpa using lp.has_sum_norm hp f }
end

instance [hp : fact (1 ≤ p)] : normed_group (lp E p) :=
normed_group.of_core _
{ norm_eq_zero_iff := norm_eq_zero_iff,
  triangle := λ f g, begin
    unfreezingI { rcases p.dichotomy with rfl | hp' },
    { cases is_empty_or_nonempty α; resetI,
      { simp [lp.eq_zero' f] },
      refine (lp.is_lub_norm (f + g)).2 _,
      rintros x ⟨i, rfl⟩,
      refine le_trans _ (add_mem_upper_bounds_add (lp.is_lub_norm f).1 (lp.is_lub_norm g).1
        ⟨_, _, ⟨i, rfl⟩, ⟨i, rfl⟩, rfl⟩),
      exact norm_add_le (f i) (g i) },
    { have hp'' : 0 < p.to_real := zero_lt_one.trans_le hp',
      have hf₁ : ∀ i, 0 ≤ ∥f i∥ := λ i, norm_nonneg _,
      have hg₁ : ∀ i, 0 ≤ ∥g i∥ := λ i, norm_nonneg _,
      have hf₂ := lp.has_sum_norm hp'' f,
      have hg₂ := lp.has_sum_norm hp'' g,
      -- apply Minkowski's inequality
      obtain ⟨C, hC₁, hC₂, hCfg⟩ :=
        real.Lp_add_le_has_sum_of_nonneg hp' hf₁ hg₁ (norm_nonneg' _) (norm_nonneg' _) hf₂ hg₂,
      refine le_trans _ hC₂,
      rw ← real.rpow_le_rpow_iff (norm_nonneg' (f + g)) hC₁ hp'',
      refine has_sum_le _ (lp.has_sum_norm hp'' (f + g)) hCfg,
      intros i,
      exact real.rpow_le_rpow (norm_nonneg _) (norm_add_le _ _) hp''.le },
  end,
  norm_neg := norm_neg }

-- TODO: define an `ennreal` version of `is_conjugate_exponent`, and then express this inequality
-- in a better version which also covers the case `p = 1, q = ∞`.
/-- Hölder inequality -/
protected lemma tsum_mul_le_mul_norm {p q : ℝ≥0∞}
  (hpq : p.to_real.is_conjugate_exponent q.to_real) (f : lp E p) (g : lp E q) :
  summable (λ i, ∥f i∥ * ∥g i∥) ∧ ∑' i, ∥f i∥ * ∥g i∥ ≤ ∥f∥ * ∥g∥ :=
begin
  have hf₁ : ∀ i, 0 ≤ ∥f i∥ := λ i, norm_nonneg _,
  have hg₁ : ∀ i, 0 ≤ ∥g i∥ := λ i, norm_nonneg _,
  have hf₂ := lp.has_sum_norm hpq.pos f,
  have hg₂ := lp.has_sum_norm hpq.symm.pos g,
  obtain ⟨C, -, hC', hC⟩ :=
    real.inner_le_Lp_mul_Lq_has_sum_of_nonneg hpq (norm_nonneg' _) (norm_nonneg' _) hf₁ hg₁ hf₂ hg₂,
  rw ← hC.tsum_eq at hC',
  exact ⟨hC.summable, hC'⟩
end

protected lemma summable_mul {p q : ℝ≥0∞}
  (hpq : p.to_real.is_conjugate_exponent q.to_real) (f : lp E p) (g : lp E q) :
  summable (λ i, ∥f i∥ * ∥g i∥) :=
(lp.tsum_mul_le_mul_norm hpq f g).1

protected lemma tsum_mul_le_mul_norm' {p q : ℝ≥0∞}
  (hpq : p.to_real.is_conjugate_exponent q.to_real) (f : lp E p) (g : lp E q) :
  ∑' i, ∥f i∥ * ∥g i∥ ≤ ∥f∥ * ∥g∥ :=
(lp.tsum_mul_le_mul_norm hpq f g).2

section compare_pointwise

lemma norm_apply_le_norm (hp : p ≠ 0) (f : lp E p) (i : α) : ∥f i∥ ≤ ∥f∥ :=
begin
  rcases eq_or_ne p ∞ with rfl | hp',
  { haveI : nonempty α := ⟨i⟩,
    exact (is_lub_norm f).1 ⟨i, rfl⟩ },
  have hp'' : 0 < p.to_real := ennreal.to_real_pos hp hp',
  have : ∀ i, 0 ≤ ∥f i∥ ^ p.to_real,
  { exact λ i, real.rpow_nonneg_of_nonneg (norm_nonneg _) _ },
  rw ← real.rpow_le_rpow_iff (norm_nonneg _) (norm_nonneg' _) hp'',
  convert le_has_sum (has_sum_norm hp'' f) i (λ i hi, this i),
end

lemma sum_rpow_le_norm_rpow (hp : 0 < p.to_real) (f : lp E p) (s : finset α) :
  ∑ i in s, ∥f i∥ ^ p.to_real ≤ ∥f∥ ^ p.to_real :=
begin
  rw lp.norm_rpow_eq_tsum hp f,
  have : ∀ i, 0 ≤ ∥f i∥ ^ p.to_real,
  { exact λ i, real.rpow_nonneg_of_nonneg (norm_nonneg _) _ },
  refine sum_le_tsum _ (λ i hi, this i) _,
  exact (lp.mem_ℓp f).summable hp
end

lemma norm_le_of_forall_le' [nonempty α] {f : lp E ∞} (C : ℝ) (hCf : ∀ i, ∥f i∥ ≤ C) : ∥f∥ ≤ C :=
begin
  refine (is_lub_norm f).2 _,
  rintros - ⟨i, rfl⟩,
  exact hCf i,
end

lemma norm_le_of_forall_le {f : lp E ∞} {C : ℝ} (hC : 0 ≤ C) (hCf : ∀ i, ∥f i∥ ≤ C) : ∥f∥ ≤ C :=
begin
  casesI is_empty_or_nonempty α,
  { simpa [eq_zero' f] using hC, },
  { exact norm_le_of_forall_le' C hCf },
end

lemma norm_le_of_tsum_le (hp : 0 < p.to_real) {C : ℝ} (hC : 0 ≤ C) {f : lp E p}
  (hf : ∑' i, ∥f i∥ ^ p.to_real ≤ C ^ p.to_real) :
  ∥f∥ ≤ C :=
begin
  rw [← real.rpow_le_rpow_iff (norm_nonneg' _) hC hp, norm_rpow_eq_tsum hp],
  exact hf,
end

lemma norm_le_of_forall_sum_le (hp : 0 < p.to_real) {C : ℝ} (hC : 0 ≤ C) {f : lp E p}
  (hf : ∀ s : finset α, ∑ i in s, ∥f i∥ ^ p.to_real ≤ C ^ p.to_real) :
  ∥f∥ ≤ C :=
norm_le_of_tsum_le hp hC (tsum_le_of_sum_le ((lp.mem_ℓp f).summable hp) hf)

end compare_pointwise

section normed_space

variables {𝕜 : Type*} [normed_field 𝕜] [Π i, normed_space 𝕜 (E i)]

instance : module 𝕜 (pre_lp E) := pi.module α E 𝕜

lemma mem_lp_const_smul (c : 𝕜) (f : lp E p) : c • (f : pre_lp E) ∈ lp E p :=
(lp.mem_ℓp f).const_smul c

variables (E p 𝕜)

/-- The `𝕜`-submodule of elements of `Π i : α, E i` whose `lp` norm is finite.  This is `lp E p`,
with extra structure. -/
def lp_submodule : submodule 𝕜 (pre_lp E) :=
{ smul_mem' := λ c f hf, by simpa using mem_lp_const_smul c ⟨f, hf⟩,
  .. lp E p }

variables {E p 𝕜}

lemma coe_lp_submodule : (lp_submodule E p 𝕜).to_add_subgroup = lp E p := rfl

instance : module 𝕜 (lp E p) :=
{ .. (lp_submodule E p 𝕜).module }

@[simp] lemma coe_fn_smul (c : 𝕜) (f : lp E p) : ⇑(c • f) = c • f := rfl

lemma norm_const_smul (hp : p ≠ 0) {c : 𝕜} (f : lp E p) : ∥c • f∥ = ∥c∥ * ∥f∥ :=
begin
  rcases p.trichotomy with rfl | rfl | hp,
  { exact absurd rfl hp },
  { cases is_empty_or_nonempty α; resetI,
    { simp [lp.eq_zero' f], },
    apply (lp.is_lub_norm (c • f)).unique,
    convert (lp.is_lub_norm f).mul_left (norm_nonneg c),
    ext a,
    simp [coe_fn_smul, norm_smul] },
  { suffices : ∥c • f∥ ^ p.to_real = (∥c∥ * ∥f∥) ^ p.to_real,
    { refine real.rpow_left_inj_on hp.ne' _ _ this,
      { exact norm_nonneg' _ },
      { exact mul_nonneg (norm_nonneg _) (norm_nonneg' _) } },
    apply (lp.has_sum_norm hp (c • f)).unique,
    convert (lp.has_sum_norm hp f).mul_left (∥c∥ ^ p.to_real),
    { simp [coe_fn_smul, norm_smul, real.mul_rpow (norm_nonneg c) (norm_nonneg _)] },
    have hf : 0 ≤ ∥f∥ := lp.norm_nonneg' f,
    simp [coe_fn_smul, norm_smul, real.mul_rpow (norm_nonneg c) hf] }
end

instance [fact (1 ≤ p)] : normed_space 𝕜 (lp E p) :=
{ norm_smul_le := λ c f, begin
    have hp : 0 < p := ennreal.zero_lt_one.trans_le (fact.out _),
    simp [norm_const_smul hp.ne']
  end }

variables {𝕜' : Type*} [normed_field 𝕜']

instance [Π i, normed_space 𝕜' (E i)] [has_smul 𝕜' 𝕜] [Π i, is_scalar_tower 𝕜' 𝕜 (E i)] :
  is_scalar_tower 𝕜' 𝕜 (lp E p) :=
begin
  refine ⟨λ r c f, _⟩,
  ext1,
  exact (lp.coe_fn_smul _ _).trans (smul_assoc _ _ _)
end

end normed_space

section non_unital_normed_ring

variables {I : Type*} {B : I → Type*} [Π i, non_unital_normed_ring (B i)]

lemma _root_.mem_ℓp.infty_mul {f g : Π i, B i} (hf : mem_ℓp f ∞) (hg : mem_ℓp g ∞) :
  mem_ℓp (f * g) ∞ :=
begin
  rw mem_ℓp_infty_iff,
  obtain ⟨⟨Cf, hCf⟩, ⟨Cg, hCg⟩⟩ := ⟨hf.bdd_above, hg.bdd_above⟩,
  refine ⟨Cf * Cg, _⟩,
  rintros _ ⟨i, rfl⟩,
  calc ∥(f * g) i∥ ≤ ∥f i∥ * ∥g i∥ : norm_mul_le (f i) (g i)
  ...             ≤ Cf * Cg       : mul_le_mul (hCf ⟨i, rfl⟩) (hCg ⟨i, rfl⟩) (norm_nonneg _)
                                      ((norm_nonneg _).trans (hCf ⟨i, rfl⟩))
end

instance : has_mul (lp B ∞) :=
{ mul := λ f g, ⟨(f  * g : Π i, B i) , f.property.infty_mul g.property⟩}

@[simp] lemma infty_coe_fn_mul (f g : lp B ∞) : ⇑(f * g) = f * g := rfl

instance : non_unital_ring (lp B ∞) :=
function.injective.non_unital_ring lp.has_coe_to_fun.coe (subtype.coe_injective)
  (lp.coe_fn_zero B ∞) lp.coe_fn_add infty_coe_fn_mul lp.coe_fn_neg lp.coe_fn_sub
  (λ _ _, rfl) (λ _ _,rfl)

instance : non_unital_normed_ring (lp B ∞) :=
{ norm_mul := λ f g, lp.norm_le_of_forall_le (mul_nonneg (norm_nonneg f) (norm_nonneg g))
    (λ i, calc ∥(f * g) i∥ ≤ ∥f i∥ * ∥g i∥ : norm_mul_le _ _
    ...                    ≤ ∥f∥ * ∥g∥
    : mul_le_mul (lp.norm_apply_le_norm ennreal.top_ne_zero f i)
        (lp.norm_apply_le_norm ennreal.top_ne_zero g i) (norm_nonneg _) (norm_nonneg _)),
  .. lp.normed_group }

-- we also want a `non_unital_normed_comm_ring` instance, but this has to wait for #13719

end non_unital_normed_ring

section normed_ring

variables {I : Type*} {B : I → Type*} [Π i, normed_ring (B i)] [Π i, norm_one_class (B i)]

lemma _root_.one_mem_ℓp_infty : mem_ℓp (1 : Π i, B i) ∞ :=
⟨1, by { rintros i ⟨i, rfl⟩, exact norm_one.le,}⟩

instance : has_one (lp B ∞) :=
{ one := ⟨(1 : Π i, B i), one_mem_ℓp_infty⟩ }

@[simp] lemma infty_coe_fn_one : ⇑(1 : lp B ∞) = 1 := rfl

lemma _root_.mem_ℓp.infty_pow {f : Π i, B i} (hf : mem_ℓp f ∞) (n : ℕ) : mem_ℓp (f ^ n) ∞ :=
begin
  induction n with n hn,
  { rw pow_zero,
    exact one_mem_ℓp_infty },
  { rw pow_succ,
    exact hf.infty_mul hn }
end

instance [nonempty I] : norm_one_class (lp B ∞) :=
{ norm_one := by simp_rw [lp.norm_eq_csupr, infty_coe_fn_one, pi.one_apply, norm_one, csupr_const]}

instance : has_pow (lp B ∞) ℕ := { pow := λ f n, ⟨_, f.prop.infty_pow n⟩ }

@[simp] lemma infty_coe_fn_pow (f : lp B ∞) (n : ℕ) : ⇑(f ^ n) = f ^ n := rfl

lemma _root_.nat_cast_mem_ℓp_infty : ∀ (n : ℕ), mem_ℓp (n : Π i, B i) ∞
| 0 := by { rw nat.cast_zero, exact zero_mem_ℓp }
| (n + 1) := by { rw nat.cast_succ, exact (_root_.nat_cast_mem_ℓp_infty n).add one_mem_ℓp_infty }

instance : has_nat_cast (lp B ∞) := { nat_cast := λ n, ⟨(↑n : Π i, B i), nat_cast_mem_ℓp_infty _⟩ }

@[simp] lemma infty_coe_fn_nat_cast (n : ℕ) : ⇑(n : lp B ∞) = n := rfl

lemma _root_.int_cast_mem_ℓp_infty (z : ℤ) : mem_ℓp (z : Π i, B i) ∞ :=
begin
  obtain ⟨n, rfl | rfl⟩ := z.eq_coe_or_neg,
  { rw int.cast_coe_nat,
    exact nat_cast_mem_ℓp_infty n },
  { rw [int.cast_neg, int.cast_coe_nat],
    exact (nat_cast_mem_ℓp_infty n).neg }
end

instance : has_int_cast (lp B ∞) := { int_cast := λ z, ⟨(↑z : Π i, B i), int_cast_mem_ℓp_infty _⟩ }

@[simp] lemma infty_coe_fn_int_cast (z : ℤ) : ⇑(z : lp B ∞) = z := rfl

instance : ring (lp B ∞) :=
function.injective.ring lp.has_coe_to_fun.coe subtype.coe_injective
  (lp.coe_fn_zero B ∞) (infty_coe_fn_one) lp.coe_fn_add infty_coe_fn_mul
  lp.coe_fn_neg lp.coe_fn_sub (λ _ _, rfl) (λ _ _, rfl) infty_coe_fn_pow
  infty_coe_fn_nat_cast infty_coe_fn_int_cast

instance : normed_ring (lp B ∞) :=
{ .. lp.ring, .. lp.non_unital_normed_ring }

end normed_ring

section normed_comm_ring

variables {I : Type*} {B : I → Type*} [Π i, normed_comm_ring (B i)] [∀ i, norm_one_class (B i)]

instance : comm_ring (lp B ∞) :=
{ mul_comm := λ f g, by { ext, simp only [lp.infty_coe_fn_mul, pi.mul_apply, mul_comm] },
  .. lp.ring }

instance : normed_comm_ring (lp B ∞) :=
{ .. lp.comm_ring, .. lp.normed_ring }

end normed_comm_ring

section single
variables {𝕜 : Type*} [normed_field 𝕜] [Π i, normed_space 𝕜 (E i)]
variables [decidable_eq α]

/-- The element of `lp E p` which is `a : E i` at the index `i`, and zero elsewhere. -/
protected def single (p) (i : α) (a : E i) : lp E p :=
⟨ λ j, if h : j = i then eq.rec a h.symm else 0,
  begin
    refine (mem_ℓp_zero _).of_exponent_ge (zero_le p),
    refine (set.finite_singleton i).subset _,
    intros j,
    simp only [forall_exists_index, set.mem_singleton_iff, ne.def, dite_eq_right_iff,
      set.mem_set_of_eq, not_forall],
    rintros rfl,
    simp,
  end ⟩

protected lemma single_apply (p) (i : α) (a : E i) (j : α) :
  lp.single p i a j = if h : j = i then eq.rec a h.symm else 0 :=
rfl

protected lemma single_apply_self (p) (i : α) (a : E i) :
  lp.single p i a i = a :=
by rw [lp.single_apply, dif_pos rfl]

protected lemma single_apply_ne (p) (i : α) (a : E i) {j : α} (hij : j ≠ i) :
  lp.single p i a j = 0 :=
by rw [lp.single_apply, dif_neg hij]

@[simp] protected lemma single_neg (p) (i : α) (a : E i) :
  lp.single p i (- a) = - lp.single p i a :=
begin
  ext j,
  by_cases hi : j = i,
  { subst hi,
    simp [lp.single_apply_self] },
  { simp [lp.single_apply_ne p i _ hi] }
end

@[simp] protected lemma single_smul (p) (i : α) (a : E i) (c : 𝕜) :
  lp.single p i (c • a) = c • lp.single p i a :=
begin
  ext j,
  by_cases hi : j = i,
  { subst hi,
    simp [lp.single_apply_self] },
  { simp [lp.single_apply_ne p i _ hi] }
end

protected lemma norm_sum_single (hp : 0 < p.to_real) (f : Π i, E i) (s : finset α) :
  ∥∑ i in s, lp.single p i (f i)∥ ^ p.to_real = ∑ i in s, ∥f i∥ ^ p.to_real :=
begin
  refine (has_sum_norm hp (∑ i in s, lp.single p i (f i))).unique _,
  simp only [lp.single_apply, coe_fn_sum, finset.sum_apply, finset.sum_dite_eq],
  have h : ∀ i ∉ s, ∥ite (i ∈ s) (f i) 0∥ ^ p.to_real = 0,
  { intros i hi,
    simp [if_neg hi, real.zero_rpow hp.ne'], },
  have h' : ∀ i ∈ s, ∥f i∥ ^ p.to_real = ∥ite (i ∈ s) (f i) 0∥ ^ p.to_real,
  { intros i hi,
    rw if_pos hi },
  simpa [finset.sum_congr rfl h'] using has_sum_sum_of_ne_finset_zero h,
end

protected lemma norm_single (hp : 0 < p.to_real) (f : Π i, E i) (i : α) :
  ∥lp.single p i (f i)∥ = ∥f i∥ :=
begin
  refine real.rpow_left_inj_on hp.ne' (norm_nonneg' _) (norm_nonneg _) _,
  simpa using lp.norm_sum_single hp f {i},
end

protected lemma norm_sub_norm_compl_sub_single (hp : 0 < p.to_real) (f : lp E p) (s : finset α) :
  ∥f∥ ^ p.to_real - ∥f - ∑ i in s, lp.single p i (f i)∥ ^ p.to_real = ∑ i in s, ∥f i∥ ^ p.to_real :=
begin
  refine ((has_sum_norm hp f).sub (has_sum_norm hp (f - ∑ i in s, lp.single p i (f i)))).unique _,
  let F : α → ℝ := λ i, ∥f i∥ ^ p.to_real - ∥(f - ∑ i in s, lp.single p i (f i)) i∥ ^ p.to_real,
  have hF : ∀ i ∉ s, F i = 0,
  { intros i hi,
    suffices : ∥f i∥ ^ p.to_real - ∥f i - ite (i ∈ s) (f i) 0∥ ^ p.to_real = 0,
    { simpa [F, coe_fn_sum, lp.single_apply] using this, },
    simp [if_neg hi] },
  have hF' : ∀ i ∈ s, F i = ∥f i∥ ^ p.to_real,
  { intros i hi,
    simp [F, coe_fn_sum, lp.single_apply, if_pos hi, real.zero_rpow hp.ne'] },
  have : has_sum F (∑ i in s, F i) := has_sum_sum_of_ne_finset_zero hF,
  rwa [finset.sum_congr rfl hF'] at this,
end

protected lemma norm_compl_sum_single (hp : 0 < p.to_real) (f : lp E p) (s : finset α) :
  ∥f - ∑ i in s, lp.single p i (f i)∥ ^ p.to_real = ∥f∥ ^ p.to_real - ∑ i in s, ∥f i∥ ^ p.to_real :=
by linarith [lp.norm_sub_norm_compl_sub_single hp f s]

/-- The canonical finitely-supported approximations to an element `f` of `lp` converge to it, in the
`lp` topology. -/
protected lemma has_sum_single [fact (1 ≤ p)] (hp : p ≠ ⊤) (f : lp E p) :
  has_sum (λ i : α, lp.single p i (f i : E i)) f :=
begin
  have hp₀ : 0 < p := ennreal.zero_lt_one.trans_le (fact.out _),
  have hp' : 0 < p.to_real := ennreal.to_real_pos hp₀.ne' hp,
  have := lp.has_sum_norm hp' f,
  dsimp [has_sum] at this ⊢,
  rw metric.tendsto_nhds at this ⊢,
  intros ε hε,
  refine (this _ (real.rpow_pos_of_pos hε p.to_real)).mono _,
  intros s hs,
  rw ← real.rpow_lt_rpow_iff dist_nonneg (le_of_lt hε) hp',
  rw dist_comm at hs,
  simp only [dist_eq_norm, real.norm_eq_abs] at hs ⊢,
  have H : ∥∑ i in s, lp.single p i (f i : E i) - f∥ ^ p.to_real
    = ∥f∥ ^ p.to_real - ∑ i in s, ∥f i∥ ^ p.to_real,
  { simpa using lp.norm_compl_sum_single hp' (-f) s },
  rw ← H at hs,
  have : |∥∑ i in s, lp.single p i (f i : E i) - f∥ ^ p.to_real|
    = ∥∑ i in s, lp.single p i (f i : E i) - f∥ ^ p.to_real,
  { simp [real.abs_rpow_of_nonneg (norm_nonneg _)] },
  linarith
end

end single

section topology

open filter
open_locale topological_space uniformity

/-- The coercion from `lp E p` to `Π i, E i` is uniformly continuous. -/
lemma uniform_continuous_coe [_i : fact (1 ≤ p)] : uniform_continuous (coe : lp E p → Π i, E i) :=
begin
  have hp : p ≠ 0 := (ennreal.zero_lt_one.trans_le _i.elim).ne',
  rw uniform_continuous_pi,
  intros i,
  rw normed_group.uniformity_basis_dist.uniform_continuous_iff normed_group.uniformity_basis_dist,
  intros ε hε,
  refine ⟨ε, hε, _⟩,
  rintros f g (hfg : ∥f - g∥ < ε),
  have : ∥f i - g i∥ ≤ ∥f - g∥ := norm_apply_le_norm hp (f - g) i,
  exact this.trans_lt hfg,
end

variables {ι : Type*} {l : filter ι} [filter.ne_bot l]

lemma norm_apply_le_of_tendsto {C : ℝ} {F : ι → lp E ∞} (hCF : ∀ᶠ k in l, ∥F k∥ ≤ C)
  {f : Π a, E a} (hf : tendsto (id (λ i, F i) : ι → Π a, E a) l (𝓝 f)) (a : α) :
  ∥f a∥ ≤ C :=
begin
  have : tendsto (λ k, ∥F k a∥) l (𝓝 ∥f a∥) :=
    (tendsto.comp (continuous_apply a).continuous_at hf).norm,
  refine le_of_tendsto this (hCF.mono _),
  intros k hCFk,
  exact (norm_apply_le_norm ennreal.top_ne_zero (F k) a).trans hCFk,
end

variables [_i : fact (1 ≤ p)]

include _i

lemma sum_rpow_le_of_tendsto (hp : p ≠ ∞) {C : ℝ} {F : ι → lp E p} (hCF : ∀ᶠ k in l, ∥F k∥ ≤ C)
  {f : Π a, E a} (hf : tendsto (id (λ i, F i) : ι → Π a, E a) l (𝓝 f)) (s : finset α) :
  ∑ (i : α) in s, ∥f i∥ ^ p.to_real ≤ C ^ p.to_real :=
begin
  have hp' : p ≠ 0 := (ennreal.zero_lt_one.trans_le _i.elim).ne',
  have hp'' : 0 < p.to_real := ennreal.to_real_pos hp' hp,
  let G : (Π a, E a) → ℝ := λ f, ∑ a in s, ∥f a∥ ^ p.to_real,
  have hG : continuous G,
  { refine continuous_finset_sum s _,
    intros a ha,
    have : continuous (λ f : Π a, E a, f a):= continuous_apply a,
    exact this.norm.rpow_const (λ _, or.inr hp''.le) },
  refine le_of_tendsto (hG.continuous_at.tendsto.comp hf) _,
  refine hCF.mono _,
  intros k hCFk,
  refine (lp.sum_rpow_le_norm_rpow hp'' (F k) s).trans _,
  exact real.rpow_le_rpow (norm_nonneg _) hCFk hp''.le,
end

/-- "Semicontinuity of the `lp` norm": If all sufficiently large elements of a sequence in `lp E p`
 have `lp` norm `≤ C`, then the pointwise limit, if it exists, also has `lp` norm `≤ C`. -/
lemma norm_le_of_tendsto {C : ℝ} {F : ι → lp E p} (hCF : ∀ᶠ k in l, ∥F k∥ ≤ C) {f : lp E p}
  (hf : tendsto (id (λ i, F i) : ι → Π a, E a) l (𝓝 f)) :
  ∥f∥ ≤ C :=
begin
  obtain ⟨i, hi⟩ := hCF.exists,
  have hC : 0 ≤ C := (norm_nonneg _).trans hi,
  unfreezingI { rcases eq_top_or_lt_top p with rfl | hp },
  { apply norm_le_of_forall_le hC,
    exact norm_apply_le_of_tendsto hCF hf, },
  { have : 0 < p := ennreal.zero_lt_one.trans_le _i.elim,
    have hp' : 0 < p.to_real := ennreal.to_real_pos this.ne' hp.ne,
    apply norm_le_of_forall_sum_le hp' hC,
    exact sum_rpow_le_of_tendsto hp.ne hCF hf, }
end

/-- If `f` is the pointwise limit of a bounded sequence in `lp E p`, then `f` is in `lp E p`. -/
lemma mem_ℓp_of_tendsto {F : ι → lp E p} (hF : metric.bounded (set.range F)) {f : Π a, E a}
  (hf : tendsto (id (λ i, F i) : ι → Π a, E a) l (𝓝 f)) :
  mem_ℓp f p :=
begin
  obtain ⟨C, hC, hCF'⟩ := hF.exists_pos_norm_le,
  have hCF : ∀ k, ∥F k∥ ≤ C := λ k, hCF' _ ⟨k, rfl⟩,
  unfreezingI { rcases eq_top_or_lt_top p with rfl | hp },
  { apply mem_ℓp_infty,
    use C,
    rintros _ ⟨a, rfl⟩,
    refine norm_apply_le_of_tendsto (eventually_of_forall hCF) hf a, },
  { apply mem_ℓp_gen',
    exact sum_rpow_le_of_tendsto hp.ne (eventually_of_forall hCF) hf },
end

/-- If a sequence is Cauchy in the `lp E p` topology and pointwise convergent to a element `f` of
`lp E p`, then it converges to `f` in the `lp E p` topology. -/
lemma tendsto_lp_of_tendsto_pi {F : ℕ → lp E p} (hF : cauchy_seq F) {f : lp E p}
  (hf : tendsto (id (λ i, F i) : ℕ → Π a, E a) at_top (𝓝 f)) :
  tendsto F at_top (𝓝 f) :=
begin
  rw metric.nhds_basis_closed_ball.tendsto_right_iff,
  intros ε hε,
  have hε' : {p : (lp E p) × (lp E p) | ∥p.1 - p.2∥ < ε} ∈ 𝓤 (lp E p),
  { exact normed_group.uniformity_basis_dist.mem_of_mem hε },
  refine (hF.eventually_eventually hε').mono _,
  rintros n (hn : ∀ᶠ l in at_top, ∥(λ f, F n - f) (F l)∥ < ε),
  refine norm_le_of_tendsto (hn.mono (λ k hk, hk.le)) _,
  rw tendsto_pi_nhds,
  intros a,
  exact (hf.apply a).const_sub (F n a),
end

variables [Π a, complete_space (E a)]

instance : complete_space (lp E p) :=
metric.complete_of_cauchy_seq_tendsto
begin
  intros F hF,
  -- A Cauchy sequence in `lp E p` is pointwise convergent; let `f` be the pointwise limit.
  obtain ⟨f, hf⟩ := cauchy_seq_tendsto_of_complete (uniform_continuous_coe.comp_cauchy_seq hF),
  -- Since the Cauchy sequence is bounded, its pointwise limit `f` is in `lp E p`.
  have hf' : mem_ℓp f p := mem_ℓp_of_tendsto hF.bounded_range hf,
  -- And therefore `f` is its limit in the `lp E p` topology as well as pointwise.
  exact ⟨⟨f, hf'⟩, tendsto_lp_of_tendsto_pi hF hf⟩
end

end topology

end lp


import game.order.level04

namespace xena -- hide

/-
# Chapter 2 : Order

## Level 5

Another well-known property of the absolute value.
-/

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

/- Lemma
For any two real numbers $a$ and $b$, we have that
$$| |a| - |b| | ≤ |a - b|$$.
-/
theorem abs_of_sub_le_abs (a b : ℝ) : | |a| - |b| | ≤ |a - b| :=
begin
    have h1 : a = (a - b) + b, norm_num,
    have h2 : |a| = |(a-b) + b|, rw h1, simp,
    have h3 : |(a-b) + b | ≤ |a-b| + |b|, exact abs_add _ _,
    rw ← h2 at h3,
    have h4a : |a| - |b| ≤ |a - b|, linarith,
    clear h1 h2 h3,
    have h1 : b = (b - a) + a, norm_num,
    have h2 : |b| = |(b-a) + a|, rw h1, simp,
    have h3 : |(b-a) + a | ≤ |b-a| + |a|, exact abs_add _ _,
    rw ← h2 at h3,
    have h4b : |b| - |a| ≤ |b - a|, linarith,
    clear h1 h2 h3,
    have h1 := eq.symm ( abs_neg (a-b) ),
    have h2 : -(a-b) = b - a, norm_num,
    rw h2 at h1, clear h2,
    rw ← h1 at h4b, clear h1,
    have H : max ( |a| - |b| ) ( |b| - |a| ) ≤ | a - b |,
        simp, split, exact h4a, exact h4b,
    unfold abs,
    unfold abs at H,
    have G: -(max a (-a) - max b (-b)) = max b (-b) - max a (-a),
        norm_num,
    rw G,
    exact H, done,
end

end xena --hide


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

! This file was ported from Lean 3 source module data.matrix.hadamard
! leanprover-community/mathlib commit 3d7987cda72abc473c7cdbbb075170e9ac620042
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.LinearAlgebra.Matrix.Trace

/-!
# Hadamard product of matrices

This file defines the Hadamard product `matrix.hadamard`
and contains basic properties about them.

## Main definition

- `matrix.hadamard`: defines the Hadamard product,
  which is the pointwise product of two matrices of the same size.

## Notation

* `⊙`: the Hadamard product `matrix.hadamard`;

## References

*  <https://en.wikipedia.org/wiki/hadamard_product_(matrices)>

## Tags

hadamard product, hadamard
-/

variable {α β γ m n : Type _}

variable {R : Type _}

namespace Matrix

open Matrix BigOperators

/-- `matrix.hadamard` defines the Hadamard product,
    which is the pointwise product of two matrices of the same size.-/
@[simp]
def hadamard [Mul α] (A : Matrix m n α) (B : Matrix m n α) : Matrix m n α
  | i, j => A i j * B i j
#align matrix.hadamard Matrix.hadamard

-- mathport name: matrix.hadamard
scoped infixl:100 " ⊙ " => Matrix.hadamard

section BasicProperties

variable (A : Matrix m n α) (B : Matrix m n α) (C : Matrix m n α)

-- commutativity
theorem hadamard_comm [CommSemigroup α] : A ⊙ B = B ⊙ A :=
  ext fun _ _ => mul_comm _ _
#align matrix.hadamard_comm Matrix.hadamard_comm

-- associativity
theorem hadamard_assoc [Semigroup α] : A ⊙ B ⊙ C = A ⊙ (B ⊙ C) :=
  ext fun _ _ => mul_assoc _ _ _
#align matrix.hadamard_assoc Matrix.hadamard_assoc

-- distributivity
theorem hadamard_add [Distrib α] : A ⊙ (B + C) = A ⊙ B + A ⊙ C :=
  ext fun _ _ => left_distrib _ _ _
#align matrix.hadamard_add Matrix.hadamard_add

theorem add_hadamard [Distrib α] : (B + C) ⊙ A = B ⊙ A + C ⊙ A :=
  ext fun _ _ => right_distrib _ _ _
#align matrix.add_hadamard Matrix.add_hadamard

-- scalar multiplication
section Scalar

@[simp]
theorem smul_hadamard [Mul α] [SMul R α] [IsScalarTower R α α] (k : R) : (k • A) ⊙ B = k • A ⊙ B :=
  ext fun _ _ => smul_mul_assoc _ _ _
#align matrix.smul_hadamard Matrix.smul_hadamard

@[simp]
theorem hadamard_smul [Mul α] [SMul R α] [SMulCommClass R α α] (k : R) : A ⊙ (k • B) = k • A ⊙ B :=
  ext fun _ _ => mul_smul_comm _ _ _
#align matrix.hadamard_smul Matrix.hadamard_smul

end Scalar

section Zero

variable [MulZeroClass α]

@[simp]
theorem hadamard_zero : A ⊙ (0 : Matrix m n α) = 0 :=
  ext fun _ _ => MulZeroClass.mul_zero _
#align matrix.hadamard_zero Matrix.hadamard_zero

@[simp]
theorem zero_hadamard : (0 : Matrix m n α) ⊙ A = 0 :=
  ext fun _ _ => MulZeroClass.zero_mul _
#align matrix.zero_hadamard Matrix.zero_hadamard

end Zero

section One

variable [DecidableEq n] [MulZeroOneClass α]

variable (M : Matrix n n α)

theorem hadamard_one : M ⊙ (1 : Matrix n n α) = diagonal fun i => M i i :=
  by
  ext
  by_cases h : i = j <;> simp [h]
#align matrix.hadamard_one Matrix.hadamard_one

theorem one_hadamard : (1 : Matrix n n α) ⊙ M = diagonal fun i => M i i :=
  by
  ext
  by_cases h : i = j <;> simp [h]
#align matrix.one_hadamard Matrix.one_hadamard

end One

section Diagonal

variable [DecidableEq n] [MulZeroClass α]

theorem diagonal_hadamard_diagonal (v : n → α) (w : n → α) :
    diagonal v ⊙ diagonal w = diagonal (v * w) :=
  ext fun _ _ => (apply_ite₂ _ _ _ _ _ _).trans (congr_arg _ <| MulZeroClass.zero_mul 0)
#align matrix.diagonal_hadamard_diagonal Matrix.diagonal_hadamard_diagonal

end Diagonal

section trace

variable [Fintype m] [Fintype n]

variable (R) [Semiring α] [Semiring R] [Module R α]

theorem sum_hadamard_eq : (∑ (i : m) (j : n), (A ⊙ B) i j) = trace (A ⬝ Bᵀ) :=
  rfl
#align matrix.sum_hadamard_eq Matrix.sum_hadamard_eq

theorem dotProduct_vecMul_hadamard [DecidableEq m] [DecidableEq n] (v : m → α) (w : n → α) :
    dotProduct (vecMul v (A ⊙ B)) w = trace (diagonal v ⬝ A ⬝ (B ⬝ diagonal w)ᵀ) :=
  by
  rw [← sum_hadamard_eq, Finset.sum_comm]
  simp [dot_product, vec_mul, Finset.sum_mul, mul_assoc]
#align matrix.dot_product_vec_mul_hadamard Matrix.dotProduct_vecMul_hadamard

end trace

end BasicProperties

end Matrix


/-
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 4 : "and" (`∧`)

We learn about how to manipulate `P ∧ Q` in Lean.

## Tactics

You'll need to know about the tactics from the previous sheets,
and also the following tactics:

* `cases`
* `split`

-/

-- Throughout this sheet, `P`, `Q` and `R` will denote propositions.

variables (P Q R : Prop)

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

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

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

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

/-- `∧` is symmetric -/
example : P ∧ Q → Q ∧ P :=
begin
  sorry
end

example : P → P ∧ true :=
begin
  sorry
end

example : false → P ∧ false :=
begin
  sorry
end

/-- `∧` is transitive -/
example : (P ∧ Q) → (Q ∧ R) → (P ∧ R) :=
begin
  sorry,
end

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


/-
Copyright (c) 2021 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes
-/
import ring_theory.adjoin.polynomial
import data.mv_polynomial.variables

/-!
# Polynomials supported by a set of variables

This file contains the definition and lemmas about `mv_polynomial.supported`.

## Main definitions

* `mv_polynomial.supported` : Given a set `s : set σ`, `supported R s` is the subalgebra of
  `mv_polynomial σ R` consisting of polynomials whose set of variables is contained in `s`.
  This subalgebra is isomorphic to `mv_polynomial s R`

## Tags
variables, polynomial, vars
-/
universes u v w

namespace mv_polynomial
variables {σ τ : Type*} {R : Type u} {S : Type v} {r : R} {e : ℕ} {n m : σ}

section comm_semiring
variables [comm_semiring R] {p q : mv_polynomial σ R}

variables (R)

/-- The set of polynomials whose variables are contained in `s` as a `subalgebra` over `R`. -/
noncomputable def supported (s : set σ) : subalgebra R (mv_polynomial σ R) :=
algebra.adjoin R (X '' s)

variables {σ R}

open_locale classical
open algebra

lemma supported_eq_range_rename (s : set σ) :
  supported R s = (rename (coe : s → σ)).range :=
by rw [supported, set.image_eq_range, adjoin_range_eq_range_aeval, rename]

/--The isomorphism between the subalgebra of polynomials supported by `s` and `mv_polynomial s R`-/
noncomputable def supported_equiv_mv_polynomial (s : set σ) :
  supported R s ≃ₐ[R] mv_polynomial s R :=
(subalgebra.equiv_of_eq _ _ (supported_eq_range_rename s)).trans
(alg_equiv.of_injective (rename (coe : s → σ))
  (rename_injective _ subtype.val_injective)).symm

@[simp] lemma supported_equiv_mv_polynomial_symm_C (s : set σ) (x : R) :
  (supported_equiv_mv_polynomial s).symm (C x) = algebra_map R (supported R s) x :=
begin
  ext1,
  simp [supported_equiv_mv_polynomial, mv_polynomial.algebra_map_eq],
end

@[simp] lemma supported_equiv_mv_polynomial_symm_X (s : set σ) (i : s) :
  (↑((supported_equiv_mv_polynomial s).symm (X i : mv_polynomial s R)) : mv_polynomial σ R) = X i :=
by simp [supported_equiv_mv_polynomial]

variables {s t : set σ}

lemma mem_supported : p ∈ (supported R s) ↔ ↑p.vars ⊆ s :=
begin
  rw [supported_eq_range_rename, alg_hom.mem_range],
  split,
  { rintros ⟨p, rfl⟩,
    refine trans (finset.coe_subset.2 (vars_rename _ _)) _,
    simp },
  { intros hs,
    exact exists_rename_eq_of_vars_subset_range p (coe : s → σ) subtype.val_injective (by simpa) }
end

lemma supported_eq_vars_subset : (supported R s : set (mv_polynomial σ R)) = {p | ↑p.vars ⊆ s} :=
set.ext $ λ _, mem_supported

@[simp] lemma mem_supported_vars (p : mv_polynomial σ R) : p ∈ supported R (↑p.vars : set σ) :=
by rw [mem_supported]

variable (s)

lemma supported_eq_adjoin_X : supported R s = algebra.adjoin R (X '' s) := rfl

@[simp] lemma supported_univ : supported R (set.univ : set σ) = ⊤ :=
by simp [algebra.eq_top_iff, mem_supported]

@[simp] lemma supported_empty : supported R (∅ : set σ) = ⊥ :=
by simp [supported_eq_adjoin_X]

variables {s}

lemma supported_mono (st : s ⊆ t) : supported R s ≤ supported R t :=
algebra.adjoin_mono (set.image_subset _ st)

@[simp] lemma X_mem_supported [nontrivial R] {i : σ} : (X i) ∈ supported R s ↔ i ∈ s :=
by simp [mem_supported]

@[simp] lemma supported_le_supported_iff [nontrivial R] :
  supported R s ≤ supported R t ↔ s ⊆ t :=
begin
  split,
  { intros h i,
    simpa using @h (X i) },
  { exact supported_mono }
end

lemma supported_strict_mono [nontrivial R] :
  strict_mono (supported R : set σ → subalgebra R (mv_polynomial σ R)) :=
strict_mono_of_le_iff_le (λ _ _, supported_le_supported_iff.symm)

end comm_semiring

end mv_polynomial


import super
open tactic

set_option trace.super true
set_option profiler true

def prime (n : ℕ) := ∀ d, d ∣ n → d = 1 ∨ d = n

set_option trace.check true

lemma nat_mul_cancel_one {m n : ℕ} : m ≠ 0 → m * n = m → n = 1 :=
by cases m; super [gt, nat.zero_lt_succ,
nat.eq_of_mul_eq_mul_left, mul_one, zero_mul, nat.not_lt_zero]

lemma not_prime_zero : ¬ prime 0 :=
by intro h; cases h 2 ⟨0, by simp⟩; cases h_1

/-
example {m n : ℕ} : prime (m * n) → m = 1 ∨ n = 1 :=
by super [prime, dvd_refl, dvd_mul_right, dvd_mul_left,
nat_mul_cancel_one, not_prime_zero, mul_zero, zero_mul]
-/

example : nat.zero ≠ nat.succ nat.zero := by super [nat.zero_lt_succ, ne_of_lt]
example (x y : ℕ) : nat.succ x = nat.succ y → x = y := by super [nat.succ.inj]
example (i) (a b c : i) : [a,b,c] = [b,c,a] -> a = b ∧ b = c := by super [list.cons.inj]

definition is_positive (n : ℕ) := n > 0
example (n : ℕ) : n > 0 ↔ is_positive n := by super [is_positive]

example (m n : ℕ) : 0 + m = 0 + n → m = n :=
by super [zero_add]

example : ∀x y : ℕ, x + y = y + x :=
begin intros, have h : nat.zero = 0 := rfl, induction x,
      super [add_zero, zero_add],
      super [*, nat.add_succ, nat.succ_add] end

example (i) [inhabited i] : nonempty i := by super *
example (i) [nonempty i] : ¬(inhabited i → false) :=
by super [classical.inhabited_of_nonempty]

example : nonempty ℕ := by super
example : ¬(inhabited ℕ → false) := by super

example {a b} : ¬(b ∨ ¬a) ∨ (a → b) := by super
example {a} : a ∨ ¬a := by super
example {a} : (a ∧ a) ∨ (¬a ∧ ¬a) := by super
example {a} : ¬a → (¬a ∧ ¬a) := by super
example {a} : a ∨ a → a := by super
example (i) (c : i) (p : i → Prop) (f : i → i) :
  p c → (∀x, p x → p (f x)) → p (f (f (f c))) := by super

example (i : Type) (p : i → Prop) : ∀x, p x → ∃x, p x := by super

example (i) [nonempty i] (p : i → i → Prop) : (∀x y, p x y) → ∃x, ∀z, p x z :=
by super

example (i) [nonempty i] (p : i → Prop) : (∀x, p x) → ¬¬∀x, p x := by super *

-- Requires non-empty domain.
example {i} [nonempty i] (p : i → Prop) :
  (∀x y, p x ∨ p y) → ∃x y, p x ∧ p y := by super *

example (i) (a b : i) (p : i → Prop) (H : a = b) : p b → p a :=
by super *

example (i) (a b : i) (p : i → Prop) (H : a = b) : p a → p b :=
by super *

example (i) (a b : i) (p : i → Prop) (H : a = b) : p b = p a :=
by super *

example (i) (c : i) (p : i → Prop) (f g : i → i) :
p c → (∀x, p x → p (f x)) → (∀x, p x → f x = g x) → f (f c) = g (g c) :=
by super

-- This example from Davis-Putnam actually requires a non-empty domain

example (i) [nonempty i] (f g : i → i → Prop) :
  ∃x y, ∀z, (f x y → f y z ∧ f z z) ∧ (f x y ∧ g x y → g x z ∧ g z z) :=
by super

example (person) [nonempty person] (drinks : person → Prop) :
  ∃canary, drinks canary → ∀other, drinks other := by super

example {p q : ℕ → Prop} {r} : (∀x y, p x ∧ q y ∧ r) -> ∀x, (p x ∧ r ∧ q x) :=
by super

example {α} [add_group α] (x : α) : 0 + 0 + x + 0 + 0 + 0 = x :=
by super [add_zero, zero_add]


/-
Copyright (c) 2022 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 4 : "and" (`∧`)

We learn about how to manipulate `P ∧ Q` in Lean.

## Tactics

You'll need to know about the tactics from the previous sheets,
and also the following tactics:

* `cases`
* `split`

-/

-- Throughout this sheet, `P`, `Q` and `R` will denote propositions.

variables (P Q R : Prop)

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

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

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

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

/-- `∧` is symmetric -/
example : P ∧ Q → Q ∧ P :=
begin
  sorry
end

example : P → P ∧ true :=
begin
  sorry
end

example : false → P ∧ false :=
begin
  sorry
end

/-- `∧` is transitive -/
example : (P ∧ Q) → (Q ∧ R) → (P ∧ R) :=
begin
  sorry,
end

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


import boolalg
import init.meta.interactive_base

open boolalg

variables {A : boolalg}

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

lemma symm_diff_three (X Y Z : A) : symm_diff (symm_diff X Y) Z = X ∩ Yᶜ ∩ Zᶜ ∪ Y ∩ Xᶜ ∩ Zᶜ ∪ (Z ∩ (Xᶜ ∩ Yᶜ) ∪ Z ∩ (Y ∩ X)) :=
begin
  unfold symm_diff,
  repeat {rw diff_eq},
  repeat {rw inter_distrib_right},
  rw [compl_union, compl_inter, compl_inter, compl_compl, compl_compl],
  repeat {rw inter_distrib_left},
  repeat {rw inter_distrib_right},
  rw [inter_compl_self Y, inter_comm Xᶜ X, inter_compl_self X, bot_union, union_bot]
end

lemma symm_diff_comm (X Y : A) : symm_diff X Y = symm_diff Y X :=
  by {unfold symm_diff, rw union_comm}

lemma symm_diff_assoc (X Y Z : A) : symm_diff (symm_diff X Y) Z = symm_diff X (symm_diff Y Z) :=
begin
  rw [symm_diff_three, symm_diff_comm, symm_diff_three],
  rw [inter_comm Y Xᶜ, inter_comm Z, inter_comm Z, inter_comm Y X, inter_right_comm Y, inter_assoc Z
      , inter_comm Y, inter_comm Z, inter_comm Yᶜ, inter_comm Z Y],
    nth_rewrite 1 ←union_assoc,
    nth_rewrite 4 union_comm,
    repeat {rw ←union_assoc},
    repeat {rw ←inter_assoc}
end

lemma symm_diff_distrib_inter_left (X Y Z : A) : X ∩ (symm_diff Y Z)  = symm_diff (X ∩ Y) (X ∩ Z) :=
  by {unfold symm_diff, rw [inter_distrib_left, inter_distrib_diff, ←inter_distrib_diff, ←inter_distrib_diff]}

lemma symm_diff_distrib_inter_right (X Y Z : A) : (symm_diff X Y) ∩ Z  = symm_diff (X ∩ Z) (Y ∩ Z) :=
  by {rw [inter_comm, inter_comm X, inter_comm Y], apply symm_diff_distrib_inter_left}

lemma symm_diff_inter (X Y : A) :
  symm_diff X (X ∩ Y) = X \ Y :=
  by rw [symm_diff_alt, absorb_union_inter, diff_eq, compl_inter, inter_distrib_left,
     union_comm, union_inter_compl_self, compl_inter, inter_distrib_left, union_comm, union_inter_compl_self, ← diff_eq] -- inter_compl_self, bot_union, compl_inter, inter_distrib_left],

lemma top_symm_diff (X : A) :
  symm_diff ⊤ X = Xᶜ :=
  by {unfold symm_diff, simp only [top_diff, diff_top, union_bot]}
-----------------------------------------------

@[simp] instance to_comm_ring  : comm_ring A  :=
{
  add := λ X Y, symm_diff X Y,
  add_assoc := λ X Y Z, symm_diff_assoc X Y Z,
  zero := ⊥,
  zero_add := λ X, by {simp only [has_add.add], unfold symm_diff, rw [bot_diff, diff_bot, bot_union]},--rw [bot_union, boolalg.compl_bot, top_union, inter_top]},
  add_zero := λ X, by {simp only [has_add.add], unfold symm_diff, rw [bot_diff, diff_bot, union_bot]},
  neg := λ X, X,
  add_left_neg := λ X, symm_diff_self X,
  add_comm := λ X Y, symm_diff_comm X Y,
  mul := λ X Y, X ∩ Y,
  mul_assoc := λ X Y Z, inter_assoc X Y Z,
  one := ⊤,
  one_mul := λ X, top_inter X,
  mul_one := λ X, inter_top X,
  left_distrib := λ X Y Z, symm_diff_distrib_inter_left X Y Z,
  right_distrib := λ X Y Z, symm_diff_distrib_inter_right X Y Z,
  mul_comm := λ X Y, inter_comm X Y,
}

lemma one_plus (X : A) : 1 + X = Xᶜ :=
  top_symm_diff X

lemma plus_one (X : A) : X + 1 = Xᶜ :=
  by {rw add_comm, from one_plus X}

lemma top_to_boolalg : (⊤ : A) = (1 : A) := rfl

lemma bot_to_boolalg : (⊥ : A) = (0 : A) := rfl

lemma symm_diff_to_boolalg {X Y : A} :  (X \ Y) ∪ (Y \ X) = X + Y := rfl

lemma inter_to_boolalg {X Y : A} : X ∩ Y = X * Y := rfl

lemma union_to_boolalg {X Y : A} : X ∪ Y = (X + Y) + X*Y :=
  begin
    rw [add_assoc],
    nth_rewrite 1 ←one_mul Y,
    rw [←right_distrib, one_plus, ←symm_diff_to_boolalg, ←inter_to_boolalg, diff_eq, diff_eq, inter_right_comm, compl_inter],
    simp,
  end

lemma compl_to_boolalg {X : A} : Xᶜ = X + 1 :=
  (plus_one X).symm

lemma subset_to_boolalg {X Y : A} : X ⊆ Y ↔ X*Y = X :=
  by {rw ←inter_to_boolalg, exact subset_iff_inter_eq_left X Y}

lemma diff_to_boolalg {X Y : A} : X \ Y = X*(Y + 1) :=
  by rw [plus_one, ←inter_to_boolalg, diff_eq]

mk_simp_attribute ba_simp "ba_simplg"

@[simp, ba_simp] lemma times_idem (X : A) : X*X = X := inter_idem X
@[simp, ba_simp] lemma plus_zero (X : A) : X+0 = X := add_zero X
@[simp, ba_simp] lemma zero_plus (X : A) : 0+X = X := zero_add X
@[simp, ba_simp] lemma times_zero (X : A) : X*0 = 0 := inter_bot X
@[simp, ba_simp] lemma zero_times (X : A) : 0*X = 0 := bot_inter X
@[simp, ba_simp] lemma times_one (X : A) : X*1 = X := inter_top X
@[simp, ba_simp] lemma one_times (X : A) : 1*X = X := top_inter X
@[simp, ba_simp] lemma times_comm (X Y : A) : X*Y = Y*X := mul_comm X Y
@[simp, ba_simp] lemma times_assoc (X Y Z : A) : X*Y*Z = X*(Y*Z) := mul_assoc X Y Z
@[simp, ba_simp] lemma plus_comm (X Y : A) : X+Y=Y+X := add_comm X Y
@[simp, ba_simp] lemma plus_assoc (X Y Z : A) : X+Y+Z = X+(Y+Z)  := add_assoc X Y Z

@[simp, ba_simp] lemma rmult_cancel (X Y : A) : X*(X*Y) = X*Y :=
  by rw [←mul_assoc, times_idem]

@[simp] lemma two_eq_zero : (2 : A) = (0 : A) :=
  begin
    have : (1:A) + (1:A) = (2:A) := rfl, rw ←this,
    rw [one_plus, ←top_to_boolalg, ←bot_to_boolalg],
    simp,
  end

@[simp, ba_simp] lemma two_times (X : A) : 2*X = 0 := by simp

@[simp, ba_simp] lemma times_two (X : A) : X*2 = 0 := by simp

lemma neg_self (X : A) : X = -X :=
  by {have := calc X + X = X*2 : by ring ... = 0 : by simp, ring, }

@[simp, ba_simp] lemma plus_self (X : A) : X + X = 0 :=
  by {ring SOP, rw two_eq_zero, ring}

@[simp, ba_simp] lemma plus_self_left (X Y : A) : X + (X + Y )= Y :=
  by {ring, rw two_eq_zero, ring}

@[simp, ba_simp] lemma power_cancel (X : A) (n : nat) : X^(n.succ) = X :=
  by {induction n with n IH, ring, rw [pow_succ' X (nat.succ n), IH, times_idem] }

@[simp, ba_simp] lemma distrib_cancel (X Y : A) : X*Y + X*(Y+1) = X :=
  by {rw[←left_distrib], simp only [plus_self_left, times_one]}

--@[simp, ba_simp] lemma one_sandwich (X : A) : 1 + (X+1) = X := sorry

--@[simp, ba_simp] lemma mul_cancel_left (S X: A) : S*(S*X) = S*X := sorry

lemma one_side {X Y : A} : X = Y ↔ X + Y = 0 :=
  by {refine ⟨λ h, by{rw h, simp}, λ h, _⟩, rw (eq_neg_of_add_eq_zero h), exact (neg_self Y).symm }

@[simp, ba_simp] lemma prod_comp_cancel (X : A) : X*(X+1) = 0 :=
  by {ring SOP, simp }

lemma expand_product {X₁ X₂ Y₁ Y₂ S : A} : (X₁ * S + X₂ * (S+1)) * (Y₁ * S + Y₂ * (S+1)) = X₁ * Y₁ * S + X₂ * Y₂ * (S+1) :=
  by {apply one_side.mpr, ring, ring SOP, simp only with ba_simp, ring, simp}

meta def set_to_ring_eqn : tactic unit := do
`[try {simp only
    [top_to_boolalg, bot_to_boolalg, symm_diff_to_boolalg, inter_to_boolalg, union_to_boolalg,
      diff_to_boolalg, compl_to_boolalg, subset_to_boolalg] at *}]

/-
meta def normalize_boolalg_eqns : tactic unit := do
  `[set_to_ring_eqn,
    try {apply one_side.mpr},
    ring SOP]
meta def simp_only_ba_simp : tactic unit :=
  do `[simp only with ba_simp]
meta def simp_ba_simp : tactic unit :=
  do `[simp with ba_simp]
--meta def simp_ba_simp_hyp : tactic unit :=
-/