/-
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
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.measure_theory.giry_monad
import Mathlib.measure_theory.set_integral
import Mathlib.PostPort

universes u_1 u_3 u_2 u_6 u_5 u_7

namespace Mathlib

/-!
# The product measure

In this file we define and prove properties about the binary product measure. If `α` and `β` have
σ-finite measures `μ` resp. `ν` then `α × β` can be equipped with a σ-finite measure `μ.prod ν` that
satisfies `(μ.prod ν) s = ∫⁻ x, ν {y | (x, y) ∈ s} ∂μ`.
We also have `(μ.prod ν) (s.prod t) = μ s * ν t`, i.e. the measure of a rectangle is the product of
the measures of the sides.

We also prove Tonelli's theorem and Fubini's theorem.

## Main definition

* `measure_theory.measure.prod`: The product of two measures.

## Main results

* `measure_theory.measure.prod_apply` states `μ.prod ν s = ∫⁻ x, ν {y | (x, y) ∈ s} ∂μ`
  for measurable `s`. `measure_theory.measure.prod_apply_symm` is the reversed version.
* `measure_theory.measure.prod_prod` states `μ.prod ν (s.prod t) = μ s * ν t` for measurable sets
  `s` and `t`.
* `measure_theory.lintegral_prod`: Tonelli's theorem. It states that for a measurable function
  `α × β → ennreal` we have `∫⁻ z, f z ∂(μ.prod ν) = ∫⁻ x, ∫⁻ y, f (x, y) ∂ν ∂μ`. The version
  for functions `α → β → ennreal` is reversed, and called `lintegral_lintegral`. Both versions have
  a variant with `_symm` appended, where the order of integration is reversed.
  The lemma `measurable.lintegral_prod_right'` states that the inner integral of the right-hand side
  is measurable.
* `measure_theory.integrable_prod_iff` states that a binary function is integrable iff both
  * `y ↦ f (x, y)` is integrable for almost every `x`, and
  * the function `x ↦ ∫ ∥f (x, y)∥ dy` is integrable.
* `measure_theory.integral_prod`: Fubini's theorem. It states that for a integrable function
  `α × β → E` (where `E` is a second countable Banach space) we have
  `∫ z, f z ∂(μ.prod ν) = ∫ x, ∫ y, f (x, y) ∂ν ∂μ`. This theorem has the same variants as
  Tonelli's theorem. The lemma `measure_theory.integrable.integral_prod_right` states that the
  inner integral of the right-hand side is integrable.

## Implementation Notes

Many results are proven twice, once for functions in curried form (`α → β → γ`) and one for
functions in uncurried form (`α × β → γ`). The former often has an assumption
`measurable (uncurry f)`, which could be inconvenient to discharge, but for the latter it is more
common that the function has to be given explicitly, since Lean cannot synthesize the function by
itself. We name the lemmas about the uncurried form with a prime.
Tonelli's theorem and Fubini's theorem have a different naming scheme, since the version for the
uncurried version is reversed.

## Tags

product measure, Fubini's theorem, Tonelli's theorem, Fubini-Tonelli theorem
-/

/-- Rectangles formed by π-systems form a π-system. -/
theorem is_pi_system.prod {α : Type u_1} {β : Type u_3} {C : set (set α)} {D : set (set β)} (hC : is_pi_system C) (hD : is_pi_system D) : is_pi_system (set.image2 set.prod C D) := sorry

/-- Rectangles of countably spanning sets are countably spanning. -/
theorem is_countably_spanning.prod {α : Type u_1} {β : Type u_3} {C : set (set α)} {D : set (set β)} (hC : is_countably_spanning C) (hD : is_countably_spanning D) : is_countably_spanning (set.image2 set.prod C D) := sorry

/-! ### Measurability

Before we define the product measure, we can talk about the measurability of operations on binary
functions. We show that if `f` is a binary measurable function, then the function that integrates
along one of the variables (using either the Lebesgue or Bochner integral) is measurable.
-/

/-- The product of generated σ-algebras is the one generated by rectangles, if both generating sets
  are countably spanning. -/
theorem generate_from_prod_eq {α : Type u_1} {β : Type u_2} {C : set (set α)} {D : set (set β)} (hC : is_countably_spanning C) (hD : is_countably_spanning D) : prod.measurable_space = measurable_space.generate_from (set.image2 set.prod C D) := sorry

/-- If `C` and `D` generate the σ-algebras on `α` resp. `β`, then rectangles formed by `C` and `D`
  generate the σ-algebra on `α × β`. -/
theorem generate_from_eq_prod {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {C : set (set α)} {D : set (set β)} (hC : measurable_space.generate_from C = _inst_1) (hD : measurable_space.generate_from D = _inst_3) (h2C : is_countably_spanning C) (h2D : is_countably_spanning D) : measurable_space.generate_from (set.image2 set.prod C D) = prod.measurable_space := sorry

/-- The product σ-algebra is generated from boxes, i.e. `s.prod t` for sets `s : set α` and
  `t : set β`. -/
theorem generate_from_prod {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] : measurable_space.generate_from
    (set.image2 set.prod (set_of fun (s : set α) => is_measurable s) (set_of fun (t : set β) => is_measurable t)) =
  prod.measurable_space :=
  generate_from_eq_prod measurable_space.generate_from_is_measurable measurable_space.generate_from_is_measurable
    is_countably_spanning_is_measurable is_countably_spanning_is_measurable

/-- Rectangles form a π-system. -/
theorem is_pi_system_prod {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] : is_pi_system
  (set.image2 set.prod (set_of fun (s : set α) => is_measurable s) (set_of fun (t : set β) => is_measurable t)) :=
  is_pi_system.prod measurable_space.is_pi_system_is_measurable measurable_space.is_pi_system_is_measurable

/-- If `ν` is a finite measure, and `s ⊆ α × β` is measurable, then `x ↦ ν { y | (x, y) ∈ s }` is
  a measurable function. `measurable_measure_prod_mk_left` is strictly more general. -/
theorem measurable_measure_prod_mk_left_finite {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {ν : measure_theory.measure β} [measure_theory.finite_measure ν] {s : set (α × β)} (hs : is_measurable s) : measurable fun (x : α) => coe_fn ν (Prod.mk x ⁻¹' s) := sorry

/-- If `ν` is a σ-finite measure, and `s ⊆ α × β` is measurable, then `x ↦ ν { y | (x, y) ∈ s }` is
  a measurable function. -/
theorem measurable_measure_prod_mk_left {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {ν : measure_theory.measure β} [measure_theory.sigma_finite ν] {s : set (α × β)} (hs : is_measurable s) : measurable fun (x : α) => coe_fn ν (Prod.mk x ⁻¹' s) := sorry

/-- If `μ` is a σ-finite measure, and `s ⊆ α × β` is measurable, then `y ↦ μ { x | (x, y) ∈ s }` is
  a measurable function. -/
theorem measurable_measure_prod_mk_right {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure_theory.measure α} [measure_theory.sigma_finite μ] {s : set (α × β)} (hs : is_measurable s) : measurable fun (y : β) => coe_fn μ ((fun (x : α) => (x, y)) ⁻¹' s) :=
  measurable_measure_prod_mk_left (iff.mpr is_measurable_swap_iff hs)

theorem measurable.map_prod_mk_left {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {ν : measure_theory.measure β} [measure_theory.sigma_finite ν] : measurable fun (x : α) => coe_fn (measure_theory.measure.map (Prod.mk x)) ν := sorry

theorem measurable.map_prod_mk_right {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure_theory.measure α} [measure_theory.sigma_finite μ] : measurable fun (y : β) => coe_fn (measure_theory.measure.map fun (x : α) => (x, y)) μ := sorry

/-- The Lebesgue integral is measurable. This shows that the integrand of (the right-hand-side of)
  Tonelli's theorem is measurable. -/
theorem measurable.lintegral_prod_right' {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {ν : measure_theory.measure β} [measure_theory.sigma_finite ν] {f : α × β → ennreal} (hf : measurable f) : measurable fun (x : α) => measure_theory.lintegral ν fun (y : β) => f (x, y) := sorry

/-- The Lebesgue integral is measurable. This shows that the integrand of (the right-hand-side of)
  Tonelli's theorem is measurable.
  This version has the argument `f` in curried form. -/
theorem measurable.lintegral_prod_right {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {ν : measure_theory.measure β} [measure_theory.sigma_finite ν] {f : α → β → ennreal} (hf : measurable (function.uncurry f)) : measurable fun (x : α) => measure_theory.lintegral ν fun (y : β) => f x y :=
  measurable.lintegral_prod_right' hf

/-- The Lebesgue integral is measurable. This shows that the integrand of (the right-hand-side of)
  the symmetric version of Tonelli's theorem is measurable. -/
theorem measurable.lintegral_prod_left' {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure_theory.measure α} [measure_theory.sigma_finite μ] {f : α × β → ennreal} (hf : measurable f) : measurable fun (y : β) => measure_theory.lintegral μ fun (x : α) => f (x, y) :=
  measurable.lintegral_prod_right' (iff.mpr measurable_swap_iff hf)

/-- The Lebesgue integral is measurable. This shows that the integrand of (the right-hand-side of)
  the symmetric version of Tonelli's theorem is measurable.
  This version has the argument `f` in curried form. -/
theorem measurable.lintegral_prod_left {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure_theory.measure α} [measure_theory.sigma_finite μ] {f : α → β → ennreal} (hf : measurable (function.uncurry f)) : measurable fun (y : β) => measure_theory.lintegral μ fun (x : α) => f x y :=
  measurable.lintegral_prod_left' hf

theorem is_measurable_integrable {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {ν : measure_theory.measure β} [normed_group E] [measurable_space E] [measure_theory.sigma_finite ν] [opens_measurable_space E] {f : α → β → E} (hf : measurable (function.uncurry f)) : is_measurable (set_of fun (x : α) => measure_theory.integrable (f x)) := sorry

/-- The Bochner integral is measurable. This shows that the integrand of (the right-hand-side of)
  Fubini's theorem is measurable.
  This version has `f` in curried form. -/
theorem measurable.integral_prod_right {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {ν : measure_theory.measure β} [normed_group E] [measurable_space E] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [measure_theory.sigma_finite ν] {f : α → β → E} (hf : measurable (function.uncurry f)) : measurable fun (x : α) => measure_theory.integral ν fun (y : β) => f x y := sorry

/-- The Bochner integral is measurable. This shows that the integrand of (the right-hand-side of)
  Fubini's theorem is measurable. -/
theorem measurable.integral_prod_right' {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {ν : measure_theory.measure β} [normed_group E] [measurable_space E] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [measure_theory.sigma_finite ν] {f : α × β → E} (hf : measurable f) : measurable fun (x : α) => measure_theory.integral ν fun (y : β) => f (x, y) :=
  measurable.integral_prod_right (eq.mp (Eq._oldrec (Eq.refl (measurable f)) (Eq.symm (function.uncurry_curry f))) hf)

/-- The Bochner integral is measurable. This shows that the integrand of (the right-hand-side of)
  the symmetric version of Fubini's theorem is measurable.
  This version has `f` in curried form. -/
theorem measurable.integral_prod_left {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure_theory.measure α} [normed_group E] [measurable_space E] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [measure_theory.sigma_finite μ] {f : α → β → E} (hf : measurable (function.uncurry f)) : measurable fun (y : β) => measure_theory.integral μ fun (x : α) => f x y :=
  measurable.integral_prod_right' (measurable.comp hf measurable_swap)

/-- The Bochner integral is measurable. This shows that the integrand of (the right-hand-side of)
  the symmetric version of Fubini's theorem is measurable. -/
theorem measurable.integral_prod_left' {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure_theory.measure α} [normed_group E] [measurable_space E] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [measure_theory.sigma_finite μ] {f : α × β → E} (hf : measurable f) : measurable fun (y : β) => measure_theory.integral μ fun (x : α) => f (x, y) :=
  measurable.integral_prod_right' (measurable.comp hf measurable_swap)

/-! ### The product measure -/

namespace measure_theory

namespace measure

/-- The binary product of measures. They are defined for arbitrary measures, but we basically
  prove all properties under the assumption that at least one of them is σ-finite. -/
protected def prod {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] (μ : measure α) (ν : measure β) : measure (α × β) :=
  bind μ fun (x : α) => coe_fn (map (Prod.mk x)) ν

protected instance prod.measure_space {α : Type u_1} {β : Type u_2} [measure_space α] [measure_space β] : measure_space (α × β) :=
  measure_space.mk (measure.prod volume volume)

theorem prod_apply {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] {s : set (α × β)} (hs : is_measurable s) : coe_fn (measure.prod μ ν) s = lintegral μ fun (x : α) => coe_fn ν (Prod.mk x ⁻¹' s) := sorry

@[simp] theorem prod_prod {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] {s : set α} {t : set β} (hs : is_measurable s) (ht : is_measurable t) : coe_fn (measure.prod μ ν) (set.prod s t) = coe_fn μ s * coe_fn ν t := sorry

/-- If we don't assume measurability of `s` and `t`, we can bound the measure of their product. -/
theorem prod_prod_le {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] (s : set α) (t : set β) : coe_fn (measure.prod μ ν) (set.prod s t) ≤ coe_fn μ s * coe_fn ν t := sorry

theorem ae_measure_lt_top {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] {s : set (α × β)} (hs : is_measurable s) (h2s : coe_fn (measure.prod μ ν) s < ⊤) : filter.eventually (fun (x : α) => coe_fn ν (Prod.mk x ⁻¹' s) < ⊤) (ae μ) := sorry

theorem integrable_measure_prod_mk_left {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] {s : set (α × β)} (hs : is_measurable s) (h2s : coe_fn (measure.prod μ ν) s < ⊤) : integrable fun (x : α) => ennreal.to_real (coe_fn ν (Prod.mk x ⁻¹' s)) := sorry

/-- Note: the assumption `hs` cannot be dropped. For a counterexample, see
  Walter Rudin *Real and Complex Analysis*, example (c) in section 8.9. -/
theorem measure_prod_null {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] {s : set (α × β)} (hs : is_measurable s) : coe_fn (measure.prod μ ν) s = 0 ↔ filter.eventually_eq (ae μ) (fun (x : α) => coe_fn ν (Prod.mk x ⁻¹' s)) 0 := sorry

/-- Note: the converse is not true without assuming that `s` is measurable. For a counterexample,
  see Walter Rudin *Real and Complex Analysis*, example (c) in section 8.9. -/
theorem measure_ae_null_of_prod_null {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] {s : set (α × β)} (h : coe_fn (measure.prod μ ν) s = 0) : filter.eventually_eq (ae μ) (fun (x : α) => coe_fn ν (Prod.mk x ⁻¹' s)) 0 := sorry

/-- Note: the converse is not true. For a counterexample, see
  Walter Rudin *Real and Complex Analysis*, example (c) in section 8.9. -/
theorem ae_ae_of_ae_prod {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] {p : α × β → Prop} (h : filter.eventually (fun (z : α × β) => p z) (ae (measure.prod μ ν))) : filter.eventually (fun (x : α) => filter.eventually (fun (y : β) => p (x, y)) (ae ν)) (ae μ) :=
  measure_ae_null_of_prod_null h

/-- `μ.prod ν` has finite spanning sets in rectangles of finite spanning sets. -/
def finite_spanning_sets_in.prod {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} {C : set (set α)} {D : set (set β)} (hμ : finite_spanning_sets_in μ C) (hν : finite_spanning_sets_in ν D) (hC : ∀ (s : set α), s ∈ C → is_measurable s) (hD : ∀ (t : set β), t ∈ D → is_measurable t) : finite_spanning_sets_in (measure.prod μ ν) (set.image2 set.prod C D) :=
  finite_spanning_sets_in.mk
    (fun (n : ℕ) =>
      set.prod (finite_spanning_sets_in.set hμ (prod.fst (nat.unpair n)))
        (finite_spanning_sets_in.set hν (prod.snd (nat.unpair n))))
    sorry sorry sorry

protected instance prod.sigma_finite {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] : sigma_finite (measure.prod μ ν) := sorry

/-- Measures on a product space are equal the product measure if they are equal on rectangles
  with as sides sets that generate the corresponding σ-algebras. -/
theorem prod_eq_generate_from {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} {C : set (set α)} {D : set (set β)} (hC : measurable_space.generate_from C = _inst_1) (hD : measurable_space.generate_from D = _inst_3) (h2C : is_pi_system C) (h2D : is_pi_system D) (h3C : finite_spanning_sets_in μ C) (h3D : finite_spanning_sets_in ν D) {μν : measure (α × β)} (h₁ : ∀ (s : set α), s ∈ C → ∀ (t : set β), t ∈ D → coe_fn μν (set.prod s t) = coe_fn μ s * coe_fn ν t) : measure.prod μ ν = μν := sorry

/-- Measures on a product space are equal to the product measure if they are equal on rectangles. -/
theorem prod_eq {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] {μν : measure (α × β)} (h : ∀ (s : set α) (t : set β), is_measurable s → is_measurable t → coe_fn μν (set.prod s t) = coe_fn μ s * coe_fn ν t) : measure.prod μ ν = μν := sorry

theorem prod_swap {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] : coe_fn (map prod.swap) (measure.prod μ ν) = measure.prod ν μ := sorry

theorem prod_apply_symm {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] {s : set (α × β)} (hs : is_measurable s) : coe_fn (measure.prod μ ν) s = lintegral ν fun (y : β) => coe_fn μ ((fun (x : α) => (x, y)) ⁻¹' s) := sorry

theorem prod_assoc_prod {α : Type u_1} {β : Type u_3} {γ : Type u_5} [measurable_space α] [measurable_space β] [measurable_space γ] {μ : measure α} {ν : measure β} {τ : measure γ} [sigma_finite ν] [sigma_finite μ] [sigma_finite τ] : coe_fn (map ⇑measurable_equiv.prod_assoc) (measure.prod (measure.prod μ ν) τ) = measure.prod μ (measure.prod ν τ) := sorry

/-! ### The product of specific measures -/

theorem prod_restrict {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] {s : set α} {t : set β} (hs : is_measurable s) (ht : is_measurable t) : measure.prod (restrict μ s) (restrict ν t) = restrict (measure.prod μ ν) (set.prod s t) := sorry

theorem prod_dirac {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} [sigma_finite μ] (y : β) : measure.prod μ (dirac y) = coe_fn (map fun (x : α) => (x, y)) μ := sorry

theorem dirac_prod {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {ν : measure β} [sigma_finite ν] (x : α) : measure.prod (dirac x) ν = coe_fn (map (Prod.mk x)) ν := sorry

theorem dirac_prod_dirac {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {x : α} {y : β} : measure.prod (dirac x) (dirac y) = dirac (x, y) := sorry

theorem prod_sum {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} [sigma_finite μ] {ι : Type u_2} [fintype ι] (ν : ι → measure β) [∀ (i : ι), sigma_finite (ν i)] : measure.prod μ (sum ν) = sum fun (i : ι) => measure.prod μ (ν i) := sorry

theorem sum_prod {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {ν : measure β} [sigma_finite ν] {ι : Type u_2} [fintype ι] (μ : ι → measure α) [∀ (i : ι), sigma_finite (μ i)] : measure.prod (sum μ) ν = sum fun (i : ι) => measure.prod (μ i) ν := sorry

theorem prod_add {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] (ν' : measure β) [sigma_finite ν'] : measure.prod μ (ν + ν') = measure.prod μ ν + measure.prod μ ν' := sorry

theorem add_prod {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] (μ' : measure α) [sigma_finite μ'] : measure.prod (μ + μ') ν = measure.prod μ ν + measure.prod μ' ν := sorry

end measure

end measure_theory

theorem ae_measurable.prod_swap {α : Type u_1} {β : Type u_3} {γ : Type u_5} [measurable_space α] [measurable_space β] [measurable_space γ] {μ : measure_theory.measure α} {ν : measure_theory.measure β} [measure_theory.sigma_finite ν] [measure_theory.sigma_finite μ] {f : α × β → γ} (hf : ae_measurable f) : ae_measurable fun (z : β × α) => f (prod.swap z) :=
  ae_measurable.comp_measurable
    (eq.mp (Eq._oldrec (Eq.refl (ae_measurable f)) (Eq.symm measure_theory.measure.prod_swap)) hf) measurable_swap

/-- The Bochner integral is measurable. This shows that the integrand of (the right-hand-side of)
  Fubini's theorem is measurable. -/
theorem ae_measurable.integral_prod_right' {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure_theory.measure α} {ν : measure_theory.measure β} [normed_group E] [measurable_space E] [measure_theory.sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [borel_space E] [complete_space E] {f : α × β → E} (hf : ae_measurable f) : ae_measurable fun (x : α) => measure_theory.integral ν fun (y : β) => f (x, y) := sorry

theorem ae_measurable.prod_mk_left {α : Type u_1} {β : Type u_3} {γ : Type u_5} [measurable_space α] [measurable_space β] [measurable_space γ] {μ : measure_theory.measure α} {ν : measure_theory.measure β} [measure_theory.sigma_finite ν] {f : α × β → γ} (hf : ae_measurable f) : filter.eventually (fun (x : α) => ae_measurable fun (y : β) => f (x, y)) (measure_theory.measure.ae μ) := sorry

namespace measure_theory

/-! ### The Lebesgue integral on a product -/

theorem lintegral_prod_swap {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] (f : α × β → ennreal) (hf : ae_measurable f) : (lintegral (measure.prod ν μ) fun (z : β × α) => f (prod.swap z)) = lintegral (measure.prod μ ν) fun (z : α × β) => f z := sorry

/-- Tonelli's Theorem: For `ennreal`-valued measurable functions on `α × β`,
  the integral of `f` is equal to the iterated integral. -/
theorem lintegral_prod {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] (f : α × β → ennreal) (hf : measurable f) : (lintegral (measure.prod μ ν) fun (z : α × β) => f z) = lintegral μ fun (x : α) => lintegral ν fun (y : β) => f (x, y) := sorry

/-- Tonelli's Theorem: For `ennreal`-valued almost everywhere measurable functions on `α × β`,
  the integral of `f` is equal to the iterated integral. -/
theorem lintegral_prod' {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] (f : α × β → ennreal) (hf : ae_measurable f) : (lintegral (measure.prod μ ν) fun (z : α × β) => f z) = lintegral μ fun (x : α) => lintegral ν fun (y : β) => f (x, y) := sorry

/-- The symmetric verion of Tonelli's Theorem: For `ennreal`-valued almost everywhere measurable
functions on `α × β`,  the integral of `f` is equal to the iterated integral, in reverse order. -/
theorem lintegral_prod_symm' {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] (f : α × β → ennreal) (hf : ae_measurable f) : (lintegral (measure.prod μ ν) fun (z : α × β) => f z) = lintegral ν fun (y : β) => lintegral μ fun (x : α) => f (x, y) := sorry

/-- The symmetric verion of Tonelli's Theorem: For `ennreal`-valued measurable
functions on `α × β`,  the integral of `f` is equal to the iterated integral, in reverse order. -/
theorem lintegral_prod_symm {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] (f : α × β → ennreal) (hf : measurable f) : (lintegral (measure.prod μ ν) fun (z : α × β) => f z) = lintegral ν fun (y : β) => lintegral μ fun (x : α) => f (x, y) :=
  lintegral_prod_symm' f (measurable.ae_measurable hf)

/-- The reversed version of Tonelli's Theorem. In this version `f` is in curried form, which makes
  it easier for the elaborator to figure out `f` automatically. -/
theorem lintegral_lintegral {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] {f : α → β → ennreal} (hf : measurable (function.uncurry f)) : (lintegral μ fun (x : α) => lintegral ν fun (y : β) => f x y) =
  lintegral (measure.prod μ ν) fun (z : α × β) => f (prod.fst z) (prod.snd z) :=
  Eq.symm (lintegral_prod (function.uncurry f) hf)

/-- The reversed version of Tonelli's Theorem (symmetric version). In this version `f` is in curried
  form, which makes it easier for the elaborator to figure out `f` automatically. -/
theorem lintegral_lintegral_symm {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] {f : α → β → ennreal} (hf : measurable (function.uncurry f)) : (lintegral μ fun (x : α) => lintegral ν fun (y : β) => f x y) =
  lintegral (measure.prod ν μ) fun (z : β × α) => f (prod.snd z) (prod.fst z) :=
  Eq.symm (lintegral_prod_symm (function.uncurry f ∘ prod.swap) (measurable.comp hf measurable_swap))

/-- Change the order of Lebesgue integration. -/
theorem lintegral_lintegral_swap {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] [sigma_finite μ] {f : α → β → ennreal} (hf : measurable (function.uncurry f)) : (lintegral μ fun (x : α) => lintegral ν fun (y : β) => f x y) =
  lintegral ν fun (y : β) => lintegral μ fun (x : α) => f x y :=
  Eq.trans (lintegral_lintegral hf) (lintegral_prod_symm (fun (z : α × β) => f (prod.fst z) (prod.snd z)) hf)

theorem lintegral_prod_mul {α : Type u_1} {β : Type u_3} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [sigma_finite ν] {f : α → ennreal} {g : β → ennreal} (hf : measurable f) (hg : measurable g) : (lintegral (measure.prod μ ν) fun (z : α × β) => f (prod.fst z) * g (prod.snd z)) =
  (lintegral μ fun (x : α) => f x) * lintegral ν fun (y : β) => g y := sorry

/-! ### Integrability on a product -/

theorem integrable.swap {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [opens_measurable_space E] [sigma_finite μ] {f : α × β → E} (hf : integrable f) : integrable (f ∘ prod.swap) := sorry

theorem integrable_swap_iff {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [opens_measurable_space E] [sigma_finite μ] {f : α × β → E} : integrable (f ∘ prod.swap) ↔ integrable f := sorry

theorem has_finite_integral_prod_iff {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [opens_measurable_space E] {f : α × β → E} (h1f : measurable f) : has_finite_integral f ↔
  filter.eventually (fun (x : α) => has_finite_integral fun (y : β) => f (x, y)) (measure.ae μ) ∧
    has_finite_integral fun (x : α) => integral ν fun (y : β) => norm (f (x, y)) := sorry

theorem has_finite_integral_prod_iff' {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [opens_measurable_space E] {f : α × β → E} (h1f : ae_measurable f) : has_finite_integral f ↔
  filter.eventually (fun (x : α) => has_finite_integral fun (y : β) => f (x, y)) (measure.ae μ) ∧
    has_finite_integral fun (x : α) => integral ν fun (y : β) => norm (f (x, y)) := sorry

/-- A binary function is integrable if the function `y ↦ f (x, y)` is integrable for almost every
  `x` and the function `x ↦ ∫ ∥f (x, y)∥ dy` is integrable. -/
theorem integrable_prod_iff {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [opens_measurable_space E] {f : α × β → E} (h1f : ae_measurable f) : integrable f ↔
  filter.eventually (fun (x : α) => integrable fun (y : β) => f (x, y)) (measure.ae μ) ∧
    integrable fun (x : α) => integral ν fun (y : β) => norm (f (x, y)) := sorry

/-- A binary function is integrable if the function `x ↦ f (x, y)` is integrable for almost every
  `y` and the function `y ↦ ∫ ∥f (x, y)∥ dx` is integrable. -/
theorem integrable_prod_iff' {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [opens_measurable_space E] [sigma_finite μ] {f : α × β → E} (h1f : ae_measurable f) : integrable f ↔
  filter.eventually (fun (y : β) => integrable fun (x : α) => f (x, y)) (measure.ae ν) ∧
    integrable fun (y : β) => integral μ fun (x : α) => norm (f (x, y)) := sorry

theorem integrable.prod_left_ae {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [opens_measurable_space E] [sigma_finite μ] {f : α × β → E} (hf : integrable f) : filter.eventually (fun (y : β) => integrable fun (x : α) => f (x, y)) (measure.ae ν) :=
  and.left (iff.mp (integrable_prod_iff' (integrable.ae_measurable hf)) hf)

theorem integrable.prod_right_ae {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [opens_measurable_space E] [sigma_finite μ] {f : α × β → E} (hf : integrable f) : filter.eventually (fun (x : α) => integrable fun (y : β) => f (x, y)) (measure.ae μ) :=
  integrable.prod_left_ae (integrable.swap hf)

theorem integrable.integral_norm_prod_left {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [opens_measurable_space E] {f : α × β → E} (hf : integrable f) : integrable fun (x : α) => integral ν fun (y : β) => norm (f (x, y)) :=
  and.right (iff.mp (integrable_prod_iff (integrable.ae_measurable hf)) hf)

theorem integrable.integral_norm_prod_right {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [opens_measurable_space E] [sigma_finite μ] {f : α × β → E} (hf : integrable f) : integrable fun (y : β) => integral μ fun (x : α) => norm (f (x, y)) :=
  integrable.integral_norm_prod_left (integrable.swap hf)

theorem integrable.integral_prod_left {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] {f : α × β → E} (hf : integrable f) : integrable fun (x : α) => integral ν fun (y : β) => f (x, y) := sorry

theorem integrable.integral_prod_right {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] {f : α × β → E} (hf : integrable f) : integrable fun (y : β) => integral μ fun (x : α) => f (x, y) :=
  integrable.integral_prod_left (integrable.swap hf)

/-! ### The Bochner integral on a product -/

theorem integral_prod_swap {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] (f : α × β → E) (hf : ae_measurable f) : (integral (measure.prod ν μ) fun (z : β × α) => f (prod.swap z)) = integral (measure.prod μ ν) fun (z : α × β) => f z := sorry

/-! Some rules about the sum/difference of double integrals. They follow from `integral_add`, but
  we separate them out as separate lemmas, because they involve quite some steps. -/

/-- Integrals commute with addition inside another integral. `F` can be any function. -/
theorem integral_fn_integral_add {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] {E' : Type u_7} [measurable_space E'] [normed_group E'] [borel_space E'] [complete_space E'] [normed_space ℝ E'] [topological_space.second_countable_topology E'] {f : α × β → E} {g : α × β → E} (F : E → E') (hf : integrable f) (hg : integrable g) : (integral μ fun (x : α) => F (integral ν fun (y : β) => f (x, y) + g (x, y))) =
  integral μ fun (x : α) => F ((integral ν fun (y : β) => f (x, y)) + integral ν fun (y : β) => g (x, y)) := sorry

/-- Integrals commute with subtraction inside another integral.
  `F` can be any measurable function. -/
theorem integral_fn_integral_sub {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] {E' : Type u_7} [measurable_space E'] [normed_group E'] [borel_space E'] [complete_space E'] [normed_space ℝ E'] [topological_space.second_countable_topology E'] {f : α × β → E} {g : α × β → E} (F : E → E') (hf : integrable f) (hg : integrable g) : (integral μ fun (x : α) => F (integral ν fun (y : β) => f (x, y) - g (x, y))) =
  integral μ fun (x : α) => F ((integral ν fun (y : β) => f (x, y)) - integral ν fun (y : β) => g (x, y)) := sorry

/-- Integrals commute with subtraction inside a lower Lebesgue integral.
  `F` can be any function. -/
theorem lintegral_fn_integral_sub {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] {f : α × β → E} {g : α × β → E} (F : E → ennreal) (hf : integrable f) (hg : integrable g) : (lintegral μ fun (x : α) => F (integral ν fun (y : β) => f (x, y) - g (x, y))) =
  lintegral μ fun (x : α) => F ((integral ν fun (y : β) => f (x, y)) - integral ν fun (y : β) => g (x, y)) := sorry

/-- Double integrals commute with addition. -/
theorem integral_integral_add {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] {f : α × β → E} {g : α × β → E} (hf : integrable f) (hg : integrable g) : (integral μ fun (x : α) => integral ν fun (y : β) => f (x, y) + g (x, y)) =
  (integral μ fun (x : α) => integral ν fun (y : β) => f (x, y)) +
    integral μ fun (x : α) => integral ν fun (y : β) => g (x, y) :=
  Eq.trans (integral_fn_integral_add id hf hg)
    (integral_add (integrable.integral_prod_left hf) (integrable.integral_prod_left hg))

/-- Double integrals commute with addition. This is the version with `(f + g) (x, y)`
  (instead of `f (x, y) + g (x, y)`) in the LHS. -/
theorem integral_integral_add' {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] {f : α × β → E} {g : α × β → E} (hf : integrable f) (hg : integrable g) : (integral μ fun (x : α) => integral ν fun (y : β) => Add.add f g (x, y)) =
  (integral μ fun (x : α) => integral ν fun (y : β) => f (x, y)) +
    integral μ fun (x : α) => integral ν fun (y : β) => g (x, y) :=
  integral_integral_add hf hg

/-- Double integrals commute with subtraction. -/
theorem integral_integral_sub {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] {f : α × β → E} {g : α × β → E} (hf : integrable f) (hg : integrable g) : (integral μ fun (x : α) => integral ν fun (y : β) => f (x, y) - g (x, y)) =
  (integral μ fun (x : α) => integral ν fun (y : β) => f (x, y)) -
    integral μ fun (x : α) => integral ν fun (y : β) => g (x, y) :=
  Eq.trans (integral_fn_integral_sub id hf hg)
    (integral_sub (integrable.integral_prod_left hf) (integrable.integral_prod_left hg))

/-- Double integrals commute with subtraction. This is the version with `(f - g) (x, y)`
  (instead of `f (x, y) - g (x, y)`) in the LHS. -/
theorem integral_integral_sub' {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] {f : α × β → E} {g : α × β → E} (hf : integrable f) (hg : integrable g) : (integral μ fun (x : α) => integral ν fun (y : β) => Sub.sub f g (x, y)) =
  (integral μ fun (x : α) => integral ν fun (y : β) => f (x, y)) -
    integral μ fun (x : α) => integral ν fun (y : β) => g (x, y) :=
  integral_integral_sub hf hg

/-- The map that sends an L¹-function `f : α × β → E` to `∫∫f` is continuous. -/
theorem continuous_integral_integral {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] : continuous
  fun (f : l1 (α × β) E (measure.prod μ ν)) => integral μ fun (x : α) => integral ν fun (y : β) => coe_fn f (x, y) := sorry

/-- Fubini's Theorem: For integrable functions on `α × β`,
  the Bochner integral of `f` is equal to the iterated Bochner integral.
  `integrable_prod_iff` can be useful to show that the function in question in integrable.
  `measure_theory.integrable.integral_prod_right` is useful to show that the inner integral
  of the right-hand side is integrable. -/
theorem integral_prod {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] (f : α × β → E) (hf : integrable f) : (integral (measure.prod μ ν) fun (z : α × β) => f z) = integral μ fun (x : α) => integral ν fun (y : β) => f (x, y) := sorry

/-- Symmetric version of Fubini's Theorem: For integrable functions on `α × β`,
  the Bochner integral of `f` is equal to the iterated Bochner integral.
  This version has the integrals on the right-hand side in the other order. -/
theorem integral_prod_symm {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] (f : α × β → E) (hf : integrable f) : (integral (measure.prod μ ν) fun (z : α × β) => f z) = integral ν fun (y : β) => integral μ fun (x : α) => f (x, y) := sorry

/-- Reversed version of Fubini's Theorem. -/
theorem integral_integral {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] {f : α → β → E} (hf : integrable (function.uncurry f)) : (integral μ fun (x : α) => integral ν fun (y : β) => f x y) =
  integral (measure.prod μ ν) fun (z : α × β) => f (prod.fst z) (prod.snd z) :=
  Eq.symm (integral_prod (function.uncurry f) hf)

/-- Reversed version of Fubini's Theorem (symmetric version). -/
theorem integral_integral_symm {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] {f : α → β → E} (hf : integrable (function.uncurry f)) : (integral μ fun (x : α) => integral ν fun (y : β) => f x y) =
  integral (measure.prod ν μ) fun (z : β × α) => f (prod.snd z) (prod.fst z) :=
  Eq.symm (integral_prod_symm (function.uncurry f ∘ prod.swap) (integrable.swap hf))

/-- Change the order of Bochner integration. -/
theorem integral_integral_swap {α : Type u_1} {β : Type u_3} {E : Type u_6} [measurable_space α] [measurable_space β] {μ : measure α} {ν : measure β} [normed_group E] [measurable_space E] [sigma_finite ν] [topological_space.second_countable_topology E] [normed_space ℝ E] [complete_space E] [borel_space E] [sigma_finite μ] {f : α → β → E} (hf : integrable (function.uncurry f)) : (integral μ fun (x : α) => integral ν fun (y : β) => f x y) = integral ν fun (y : β) => integral μ fun (x : α) => f x y :=
  Eq.trans (integral_integral hf) (integral_prod_symm (fun (z : α × β) => f (prod.fst z) (prod.snd z)) hf)


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

import category_theory.groupoid
import algebraic_topology.fundamental_groupoid.basic
import topology.category.Top.limits
import topology.homotopy.product
import category_theory.limits.preserves.shapes.products

/-!
# Fundamental groupoid preserves products
In this file, we give the following definitions/theorems:

  - `fundamental_groupoid_functor.pi_iso` An isomorphism between Π i, (π Xᵢ) and π (Πi, Xᵢ), whose
    inverse is precisely the product of the maps π (Π i, Xᵢ) → π (Xᵢ), each induced by
    the projection in `Top` Π i, Xᵢ → Xᵢ.

  - `fundamental_groupoid_functor.prod_iso` An isomorphism between πX × πY and π (X × Y), whose
    inverse is precisely the product of the maps π (X × Y) → πX and π (X × Y) → Y, each induced by
    the projections X × Y → X and X × Y → Y

  - `fundamental_groupoid_functor.preserves_product` A proof that the fundamental groupoid functor
    preserves all products.
-/

noncomputable theory

namespace fundamental_groupoid_functor

open_locale fundamental_groupoid

universes u

section pi

variables {I : Type u} (X : I → Top.{u})

/--
The projection map Π i, X i → X i induces a map π(Π i, X i) ⟶ π(X i).
-/
def proj (i : I) : πₓ (Top.of (Π i, X i)) ⥤ πₓ (X i) := πₘ ⟨_, continuous_apply i⟩

/-- The projection map is precisely path.homotopic.proj interpreted as a functor -/
@[simp] lemma proj_map (i : I) (x₀ x₁ : πₓ (Top.of (Π i, X i))) (p : x₀ ⟶ x₁) :
  (proj X i).map p = (@path.homotopic.proj _ _ _ _ _ i p) := rfl

/--
The map taking the pi product of a family of fundamental groupoids to the fundamental
groupoid of the pi product. This is actually an isomorphism (see `pi_iso`)
-/
@[simps]
def pi_to_pi_Top : (Π i, πₓ (X i)) ⥤ πₓ (Top.of (Π i, X i)) :=
{ obj := λ g, g,
  map := λ v₁ v₂ p, path.homotopic.pi p,
  map_id' :=
  begin
    intro x,
    change path.homotopic.pi (λ i, 𝟙 (x i)) = _,
    simp only [fundamental_groupoid.id_eq_path_refl, path.homotopic.pi_lift],
    refl,
  end,
  map_comp' := λ x y z f g, (path.homotopic.comp_pi_eq_pi_comp f g).symm, }

/--
Shows `pi_to_pi_Top` is an isomorphism, whose inverse is precisely the pi product
of the induced projections. This shows that `fundamental_groupoid_functor` preserves products.
-/
@[simps]
def pi_iso : category_theory.Groupoid.of (Π i : I, πₓ (X i)) ≅ πₓ (Top.of (Π i, X i)) :=
{ hom := pi_to_pi_Top X,
  inv := category_theory.functor.pi' (proj X),
  hom_inv_id' :=
  begin
    change pi_to_pi_Top X ⋙ (category_theory.functor.pi' (proj X)) = 𝟭 _,
    apply category_theory.functor.ext; intros,
    { ext, simp, }, { refl, },
  end,
  inv_hom_id' :=
  begin
    change (category_theory.functor.pi' (proj X)) ⋙ pi_to_pi_Top X = 𝟭 _,
    apply category_theory.functor.ext; intros,
    { suffices : path.homotopic.pi ((category_theory.functor.pi' (proj X)).map f) = f, { simpa, },
      change (category_theory.functor.pi' (proj X)).map f
        with λ i, (category_theory.functor.pi' (proj X)).map f i,
      simp, }, { refl, }
  end }

section preserves
open category_theory

/-- Equivalence between the categories of cones over the objects `π Xᵢ` written in two ways -/
def cone_discrete_comp : limits.cone (discrete.functor X ⋙ π) ≌
  limits.cone (discrete.functor (λ i, πₓ (X i))) :=
limits.cones.postcompose_equivalence (discrete.comp_nat_iso_discrete X π)

lemma cone_discrete_comp_obj_map_cone :
  (cone_discrete_comp X).functor.obj ((π).map_cone (Top.pi_fan X))
  = limits.fan.mk (πₓ (Top.of (Π i, X i))) (proj X) := rfl

/-- This is `pi_iso.inv` as a cone morphism (in fact, isomorphism) -/
def pi_Top_to_pi_cone : (limits.fan.mk (πₓ (Top.of (Π i, X i))) (proj X)) ⟶
  Groupoid.pi_limit_fan (λ i : I, (πₓ (X i))) := { hom := category_theory.functor.pi' (proj X) }

instance : is_iso (pi_Top_to_pi_cone X) :=
begin
  haveI : is_iso (pi_Top_to_pi_cone X).hom := (infer_instance : is_iso (pi_iso X).inv),
  exact limits.cones.cone_iso_of_hom_iso (pi_Top_to_pi_cone X),
end

/-- The fundamental groupoid functor preserves products -/
def preserves_product : limits.preserves_limit (discrete.functor X) π :=
begin
  apply limits.preserves_limit_of_preserves_limit_cone (Top.pi_fan_is_limit X),
  apply (limits.is_limit.of_cone_equiv (cone_discrete_comp X)).to_fun,
  simp only [cone_discrete_comp_obj_map_cone],
  apply limits.is_limit.of_iso_limit _ (as_iso (pi_Top_to_pi_cone X)).symm,
  exact (Groupoid.pi_limit_cone _).is_limit,
end

end preserves

end pi

section prod

variables (A B : Top.{u})

/-- The induced map of the left projection map X × Y → X -/
def proj_left : πₓ (Top.of (A × B)) ⥤ πₓ A := πₘ ⟨_, continuous_fst⟩

/-- The induced map of the right projection map X × Y → Y -/
def proj_right : πₓ (Top.of (A × B)) ⥤ πₓ B := πₘ ⟨_, continuous_snd⟩

@[simp] lemma proj_left_map (x₀ x₁ : πₓ (Top.of (A × B))) (p : x₀ ⟶ x₁) :
  (proj_left A B).map p = path.homotopic.proj_left p := rfl

@[simp] lemma proj_right_map (x₀ x₁ : πₓ (Top.of (A × B))) (p : x₀ ⟶ x₁) :
  (proj_right A B).map p = path.homotopic.proj_right p := rfl

/--
The map taking the product of two fundamental groupoids to the fundamental groupoid of the product
of the two topological spaces. This is in fact an isomorphism (see `prod_iso`).
-/
@[simps obj]
def prod_to_prod_Top : (πₓ A) × (πₓ B) ⥤ πₓ (Top.of (A × B)) :=
{ obj := λ g, g,
  map := λ x y p, match x, y, p with
    | (x₀, x₁), (y₀, y₁), (p₀, p₁) := path.homotopic.prod p₀ p₁
  end,
  map_id' :=
  begin
    rintro ⟨x₀, x₁⟩,
    simp only [category_theory.prod_id, fundamental_groupoid.id_eq_path_refl],
    unfold_aux, rw path.homotopic.prod_lift, refl,
  end,
  map_comp' := λ x y z f g, match x, y, z, f, g with
    | (x₀, x₁), (y₀, y₁), (z₀, z₁), (f₀, f₁), (g₀, g₁) :=
    (path.homotopic.comp_prod_eq_prod_comp f₀ f₁ g₀ g₁).symm
  end }

lemma prod_to_prod_Top_map {x₀ x₁ : πₓ A} {y₀ y₁ : πₓ B}
  (p₀ : x₀ ⟶ x₁) (p₁ : y₀ ⟶ y₁) :
  @category_theory.functor.map _ _ _ _
  (prod_to_prod_Top A B) (x₀, y₀) (x₁, y₁) (p₀, p₁) = path.homotopic.prod p₀ p₁ := rfl

/--
Shows `prod_to_prod_Top` is an isomorphism, whose inverse is precisely the product
of the induced left and right projections.
-/
@[simps]
def prod_iso : category_theory.Groupoid.of ((πₓ A) × (πₓ B)) ≅ (πₓ (Top.of (A × B))) :=
{ hom := prod_to_prod_Top A B,
  inv := (proj_left A B).prod' (proj_right A B),
  hom_inv_id' :=
  begin
    change prod_to_prod_Top A B ⋙ ((proj_left A B).prod' (proj_right A B)) = 𝟭 _,
    apply category_theory.functor.hext, { intros, ext; simp; refl, },
    rintros ⟨x₀, x₁⟩ ⟨y₀, y₁⟩ ⟨f₀, f₁⟩,
    have := and.intro (path.homotopic.proj_left_prod f₀ f₁) (path.homotopic.proj_right_prod f₀ f₁),
    simpa,
  end,
  inv_hom_id' :=
  begin
    change ((proj_left A B).prod' (proj_right A B)) ⋙ prod_to_prod_Top A B = 𝟭 _,
    apply category_theory.functor.hext, { intros, ext; simp; refl, },
    rintros ⟨x₀, x₁⟩ ⟨y₀, y₁⟩ f,
    have := path.homotopic.prod_proj_left_proj_right f,
    simpa,
  end }

end prod

end fundamental_groupoid_functor


/-
Copyright (c) 2020 Yury G. Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury G. Kudryashov
-/
import analysis.normed_space.basic

/-!
# Extended norm

In this file we define a structure `enorm 𝕜 V` representing an extended norm (i.e., a norm that can
take the value `∞`) on a vector space `V` over a normed field `𝕜`. We do not use `class` for
an `enorm` because the same space can have more than one extended norm. For example, the space of
measurable functions `f : α → ℝ` has a family of `L_p` extended norms.

We prove some basic inequalities, then define

* `emetric_space` structure on `V` corresponding to `e : enorm 𝕜 V`;
* the subspace of vectors with finite norm, called `e.finite_subspace`;
* a `normed_space` structure on this space.

The last definition is an instance because the type involves `e`.

## Implementation notes

We do not define extended normed groups. They can be added to the chain once someone will need them.

## Tags

normed space, extended norm
-/

noncomputable theory
local attribute [instance, priority 1001] classical.prop_decidable
open_locale ennreal

/-- Extended norm on a vector space. As in the case of normed spaces, we require only
`‖c • x‖ ≤ ‖c‖ * ‖x‖` in the definition, then prove an equality in `map_smul`. -/
structure enorm (𝕜 : Type*) (V : Type*) [normed_field 𝕜] [add_comm_group V] [module 𝕜 V] :=
(to_fun : V → ℝ≥0∞)
(eq_zero' : ∀ x, to_fun x = 0 → x = 0)
(map_add_le' : ∀ x y : V, to_fun (x + y) ≤ to_fun x + to_fun y)
(map_smul_le' : ∀ (c : 𝕜) (x : V), to_fun (c • x) ≤ ‖c‖₊ * to_fun x)

namespace enorm

variables {𝕜 : Type*} {V : Type*} [normed_field 𝕜] [add_comm_group V] [module 𝕜 V]
  (e : enorm 𝕜 V)

instance : has_coe_to_fun (enorm 𝕜 V) (λ _, V → ℝ≥0∞) := ⟨enorm.to_fun⟩

lemma coe_fn_injective : function.injective (coe_fn : enorm 𝕜 V → (V → ℝ≥0∞)) :=
λ e₁ e₂ h, by cases e₁; cases e₂; congr; exact h

@[ext] lemma ext {e₁ e₂ : enorm 𝕜 V} (h : ∀ x, e₁ x = e₂ x) : e₁ = e₂ :=
coe_fn_injective $ funext h

lemma ext_iff {e₁ e₂ : enorm 𝕜 V} : e₁ = e₂ ↔ ∀ x, e₁ x = e₂ x :=
⟨λ h x, h ▸ rfl, ext⟩

@[simp, norm_cast] lemma coe_inj {e₁ e₂ : enorm 𝕜 V} : (e₁ : V → ℝ≥0∞) = e₂ ↔ e₁ = e₂ :=
coe_fn_injective.eq_iff

@[simp] lemma map_smul (c : 𝕜) (x : V) : e (c • x) = ‖c‖₊ * e x :=
le_antisymm (e.map_smul_le' c x) $
begin
  by_cases hc : c = 0, { simp [hc] },
  calc (‖c‖₊ : ℝ≥0∞) * e x = ‖c‖₊ * e (c⁻¹ • c • x) : by rw [inv_smul_smul₀ hc]
  ... ≤ ‖c‖₊ * (‖c⁻¹‖₊ * e (c • x)) : _
  ... = e (c • x) : _,
  { exact mul_le_mul_left' (e.map_smul_le' _ _) _ },
  { rw [← mul_assoc, nnnorm_inv, ennreal.coe_inv,
     ennreal.mul_inv_cancel _ ennreal.coe_ne_top, one_mul]; simp [hc] }
end

@[simp] lemma map_zero : e 0 = 0 :=
by { rw [← zero_smul 𝕜 (0:V), e.map_smul], norm_num }

@[simp] lemma eq_zero_iff {x : V} : e x = 0 ↔ x = 0 :=
⟨e.eq_zero' x, λ h, h.symm ▸ e.map_zero⟩

@[simp] lemma map_neg (x : V) : e (-x) = e x :=
calc e (-x) = ‖(-1 : 𝕜)‖₊ * e x : by rw [← map_smul, neg_one_smul]
        ... = e x               : by simp

lemma map_sub_rev (x y : V) : e (x - y) = e (y - x) :=
by rw [← neg_sub, e.map_neg]

lemma map_add_le (x y : V) : e (x + y) ≤ e x + e y := e.map_add_le' x y

lemma map_sub_le (x y : V) : e (x - y) ≤ e x + e y :=
calc e (x - y) = e (x + -y)   : by rw sub_eq_add_neg
           ... ≤ e x + e (-y) : e.map_add_le x (-y)
           ... = e x + e y    : by rw [e.map_neg]

instance : partial_order (enorm 𝕜 V) :=
{ le := λ e₁ e₂, ∀ x, e₁ x ≤ e₂ x,
  le_refl := λ e x, le_rfl,
  le_trans := λ e₁ e₂ e₃ h₁₂ h₂₃ x, le_trans (h₁₂ x) (h₂₃ x),
  le_antisymm := λ e₁ e₂ h₁₂ h₂₁, ext $ λ x, le_antisymm (h₁₂ x) (h₂₁ x) }

/-- The `enorm` sending each non-zero vector to infinity. -/
noncomputable instance : has_top (enorm 𝕜 V) :=
⟨{ to_fun := λ x, if x = 0 then 0 else ⊤,
   eq_zero' := λ x, by { split_ifs; simp [*] },
   map_add_le' := λ x y,
     begin
       split_ifs with hxy hx hy hy hx hy hy; try { simp [*] },
       simpa [hx, hy] using hxy
     end,
   map_smul_le' := λ c x,
     begin
       split_ifs with hcx hx hx; simp only [smul_eq_zero, not_or_distrib] at hcx,
       { simp only [mul_zero, le_refl] },
       { have : c = 0, by tauto,
         simp [this] },
       { tauto },
       { simp [hcx.1] }
     end }⟩

noncomputable instance : inhabited (enorm 𝕜 V) := ⟨⊤⟩

lemma top_map {x : V} (hx : x ≠ 0) : (⊤ : enorm 𝕜 V) x = ⊤ := if_neg hx

noncomputable instance : order_top (enorm 𝕜 V) :=
{ top := ⊤,
  le_top := λ e x, if h : x = 0 then by simp [h] else by simp [top_map h] }

noncomputable instance : semilattice_sup (enorm 𝕜 V) :=
{ le := (≤),
  lt := (<),
  sup := λ e₁ e₂,
  { to_fun := λ x, max (e₁ x) (e₂ x),
    eq_zero' := λ x h, e₁.eq_zero_iff.1 (ennreal.max_eq_zero_iff.1 h).1,
    map_add_le' := λ x y, max_le
      (le_trans (e₁.map_add_le _ _) $ add_le_add (le_max_left _ _) (le_max_left _ _))
      (le_trans (e₂.map_add_le _ _) $ add_le_add (le_max_right _ _) (le_max_right _ _)),
    map_smul_le' := λ c x, le_of_eq $ by simp only [map_smul, ennreal.mul_max] },
  le_sup_left := λ e₁ e₂ x, le_max_left _ _,
  le_sup_right := λ e₁ e₂ x, le_max_right _ _,
  sup_le := λ e₁ e₂ e₃ h₁ h₂ x, max_le (h₁ x) (h₂ x),
  .. enorm.partial_order }

@[simp, norm_cast]

@[norm_cast]
lemma max_map (e₁ e₂ : enorm 𝕜 V) (x : V) : (e₁ ⊔ e₂) x = max (e₁ x) (e₂ x) := rfl

/-- Structure of an `emetric_space` defined by an extended norm. -/
@[reducible] def emetric_space : emetric_space V :=
{ edist := λ x y, e (x - y),
  edist_self := λ x, by simp,
  eq_of_edist_eq_zero := λ x y, by simp [sub_eq_zero],
  edist_comm := e.map_sub_rev,
  edist_triangle := λ x y z,
    calc e (x - z) = e ((x - y) + (y - z)) : by rw [sub_add_sub_cancel]
               ... ≤ e (x - y) + e (y - z) : e.map_add_le (x - y) (y - z) }

/-- The subspace of vectors with finite enorm. -/
def finite_subspace : subspace 𝕜 V :=
{ carrier   := {x | e x < ⊤},
  zero_mem' := by simp,
  add_mem'  := λ x y hx hy, lt_of_le_of_lt (e.map_add_le x y) (ennreal.add_lt_top.2 ⟨hx, hy⟩),
  smul_mem' := λ c x (hx : _ < _),
    calc e (c • x) = ‖c‖₊ * e x : e.map_smul c x
               ... < ⊤              : ennreal.mul_lt_top ennreal.coe_ne_top hx.ne }

/-- Metric space structure on `e.finite_subspace`. We use `emetric_space.to_metric_space`
to ensure that this definition agrees with `e.emetric_space`. -/
instance : metric_space e.finite_subspace :=
begin
  letI := e.emetric_space,
  refine emetric_space.to_metric_space (λ x y, _),
  change e (x - y) ≠ ⊤,
  exact ne_top_of_le_ne_top (ennreal.add_lt_top.2 ⟨x.2, y.2⟩).ne (e.map_sub_le x y)
end

lemma finite_dist_eq (x y : e.finite_subspace) : dist x y = (e (x - y)).to_real := rfl

lemma finite_edist_eq (x y : e.finite_subspace) : edist x y = e (x - y) := rfl

/-- Normed group instance on `e.finite_subspace`. -/
instance : normed_add_comm_group e.finite_subspace :=
{ norm := λ x, (e x).to_real,
  dist_eq := λ x y, rfl,
  .. finite_subspace.metric_space e, .. submodule.add_comm_group _ }

lemma finite_norm_eq (x : e.finite_subspace) : ‖x‖ = (e x).to_real := rfl

/-- Normed space instance on `e.finite_subspace`. -/
instance : normed_space 𝕜 e.finite_subspace :=
{ norm_smul_le := λ c x, le_of_eq $ by simp [finite_norm_eq, ennreal.to_real_mul] }

end enorm


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

! This file was ported from Lean 3 source module topology.algebra.uniform_mul_action
! leanprover-community/mathlib commit f47581155c818e6361af4e4fda60d27d020c226b
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Topology.Algebra.UniformGroup
import Mathbin.Topology.UniformSpace.Completion

/-!
# Multiplicative action on the completion of a uniform space

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

In this file we define typeclasses `has_uniform_continuous_const_vadd` and
`has_uniform_continuous_const_smul` and prove that a multiplicative action on `X` with uniformly
continuous `(•) c` can be extended to a multiplicative action on `uniform_space.completion X`.

In later files once the additive group structure is set up, we provide
* `uniform_space.completion.distrib_mul_action`
* `uniform_space.completion.mul_action_with_zero`
* `uniform_space.completion.module`

TODO: Generalise the results here from the concrete `completion` to any `abstract_completion`.
-/

universe u v w x y z

noncomputable section

variable (R : Type u) (M : Type v) (N : Type w) (X : Type x) (Y : Type y) [UniformSpace X]
  [UniformSpace Y]

#print UniformContinuousConstVAdd /-
/-- An additive action such that for all `c`, the map `λ x, c +ᵥ x` is uniformly continuous. -/
class UniformContinuousConstVAdd [VAdd M X] : Prop where
  uniformContinuous_const_vadd : ∀ c : M, UniformContinuous ((· +ᵥ ·) c : X → X)
#align has_uniform_continuous_const_vadd UniformContinuousConstVAdd
-/

#print UniformContinuousConstSMul /-
/-- A multiplicative action such that for all `c`, the map `λ x, c • x` is uniformly continuous. -/
@[to_additive]
class UniformContinuousConstSMul [SMul M X] : Prop where
  uniformContinuous_const_smul : ∀ c : M, UniformContinuous ((· • ·) c : X → X)
#align has_uniform_continuous_const_smul UniformContinuousConstSMul
#align has_uniform_continuous_const_vadd UniformContinuousConstVAdd
-/

export UniformContinuousConstVAdd (uniformContinuous_const_vadd)

export UniformContinuousConstSMul (uniformContinuous_const_smul)

#print AddMonoid.uniformContinuousConstSMul_nat /-
instance AddMonoid.uniformContinuousConstSMul_nat [AddGroup X] [UniformAddGroup X] :
    UniformContinuousConstSMul ℕ X :=
  ⟨uniformContinuous_const_nsmul⟩
#align add_monoid.has_uniform_continuous_const_smul_nat AddMonoid.uniformContinuousConstSMul_nat
-/

#print AddGroup.uniformContinuousConstSMul_int /-
instance AddGroup.uniformContinuousConstSMul_int [AddGroup X] [UniformAddGroup X] :
    UniformContinuousConstSMul ℤ X :=
  ⟨uniformContinuous_const_zsmul⟩
#align add_group.has_uniform_continuous_const_smul_int AddGroup.uniformContinuousConstSMul_int
-/

/- warning: has_uniform_continuous_const_smul_of_continuous_const_smul -> uniformContinuousConstSMul_of_continuousConstSMul is a dubious translation:
lean 3 declaration is
  forall (R : Type.{u1}) (M : Type.{u2}) [_inst_3 : Monoid.{u1} R] [_inst_4 : AddCommGroup.{u2} M] [_inst_5 : DistribMulAction.{u1, u2} R M _inst_3 (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4)))] [_inst_6 : UniformSpace.{u2} M] [_inst_7 : UniformAddGroup.{u2} M _inst_6 (AddCommGroup.toAddGroup.{u2} M _inst_4)] [_inst_8 : ContinuousConstSMul.{u1, u2} R M (UniformSpace.toTopologicalSpace.{u2} M _inst_6) (SMulZeroClass.toHasSmul.{u1, u2} R M (AddZeroClass.toHasZero.{u2} M (AddMonoid.toAddZeroClass.{u2} M (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4))))) (DistribSMul.toSmulZeroClass.{u1, u2} R M (AddMonoid.toAddZeroClass.{u2} M (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4)))) (DistribMulAction.toDistribSMul.{u1, u2} R M _inst_3 (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4))) _inst_5)))], UniformContinuousConstSMul.{u1, u2} R M _inst_6 (SMulZeroClass.toHasSmul.{u1, u2} R M (AddZeroClass.toHasZero.{u2} M (AddMonoid.toAddZeroClass.{u2} M (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4))))) (DistribSMul.toSmulZeroClass.{u1, u2} R M (AddMonoid.toAddZeroClass.{u2} M (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4)))) (DistribMulAction.toDistribSMul.{u1, u2} R M _inst_3 (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4))) _inst_5)))
but is expected to have type
  forall (R : Type.{u1}) (M : Type.{u2}) [_inst_3 : Monoid.{u1} R] [_inst_4 : AddCommGroup.{u2} M] [_inst_5 : DistribMulAction.{u1, u2} R M _inst_3 (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4)))] [_inst_6 : UniformSpace.{u2} M] [_inst_7 : UniformAddGroup.{u2} M _inst_6 (AddCommGroup.toAddGroup.{u2} M _inst_4)] [_inst_8 : ContinuousConstSMul.{u1, u2} R M (UniformSpace.toTopologicalSpace.{u2} M _inst_6) (SMulZeroClass.toSMul.{u1, u2} R M (NegZeroClass.toZero.{u2} M (SubNegZeroMonoid.toNegZeroClass.{u2} M (SubtractionMonoid.toSubNegZeroMonoid.{u2} M (SubtractionCommMonoid.toSubtractionMonoid.{u2} M (AddCommGroup.toDivisionAddCommMonoid.{u2} M _inst_4))))) (DistribSMul.toSMulZeroClass.{u1, u2} R M (AddMonoid.toAddZeroClass.{u2} M (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4)))) (DistribMulAction.toDistribSMul.{u1, u2} R M _inst_3 (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4))) _inst_5)))], UniformContinuousConstSMul.{u1, u2} R M _inst_6 (SMulZeroClass.toSMul.{u1, u2} R M (NegZeroClass.toZero.{u2} M (SubNegZeroMonoid.toNegZeroClass.{u2} M (SubtractionMonoid.toSubNegZeroMonoid.{u2} M (SubtractionCommMonoid.toSubtractionMonoid.{u2} M (AddCommGroup.toDivisionAddCommMonoid.{u2} M _inst_4))))) (DistribSMul.toSMulZeroClass.{u1, u2} R M (AddMonoid.toAddZeroClass.{u2} M (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4)))) (DistribMulAction.toDistribSMul.{u1, u2} R M _inst_3 (SubNegMonoid.toAddMonoid.{u2} M (AddGroup.toSubNegMonoid.{u2} M (AddCommGroup.toAddGroup.{u2} M _inst_4))) _inst_5)))
Case conversion may be inaccurate. Consider using '#align has_uniform_continuous_const_smul_of_continuous_const_smul uniformContinuousConstSMul_of_continuousConstSMulₓ'. -/
/-- A `distrib_mul_action` that is continuous on a uniform group is uniformly continuous.
This can't be an instance due to it forming a loop with
`has_uniform_continuous_const_smul.to_has_continuous_const_smul` -/
theorem uniformContinuousConstSMul_of_continuousConstSMul [Monoid R] [AddCommGroup M]
    [DistribMulAction R M] [UniformSpace M] [UniformAddGroup M] [ContinuousConstSMul R M] :
    UniformContinuousConstSMul R M :=
  ⟨fun r =>
    uniformContinuous_of_continuousAt_zero (DistribMulAction.toAddMonoidHom M r)
      (Continuous.continuousAt (continuous_const_smul r))⟩
#align has_uniform_continuous_const_smul_of_continuous_const_smul uniformContinuousConstSMul_of_continuousConstSMul

/- warning: ring.has_uniform_continuous_const_smul -> Ring.uniformContinuousConstSMul is a dubious translation:
lean 3 declaration is
  forall (R : Type.{u1}) [_inst_3 : Ring.{u1} R] [_inst_4 : UniformSpace.{u1} R] [_inst_5 : UniformAddGroup.{u1} R _inst_4 (AddGroupWithOne.toAddGroup.{u1} R (AddCommGroupWithOne.toAddGroupWithOne.{u1} R (Ring.toAddCommGroupWithOne.{u1} R _inst_3)))] [_inst_6 : ContinuousMul.{u1} R (UniformSpace.toTopologicalSpace.{u1} R _inst_4) (Distrib.toHasMul.{u1} R (Ring.toDistrib.{u1} R _inst_3))], UniformContinuousConstSMul.{u1, u1} R R _inst_4 (Mul.toSMul.{u1} R (Distrib.toHasMul.{u1} R (Ring.toDistrib.{u1} R _inst_3)))
but is expected to have type
  forall (R : Type.{u1}) [_inst_3 : Ring.{u1} R] [_inst_4 : UniformSpace.{u1} R] [_inst_5 : UniformAddGroup.{u1} R _inst_4 (AddGroupWithOne.toAddGroup.{u1} R (Ring.toAddGroupWithOne.{u1} R _inst_3))] [_inst_6 : ContinuousMul.{u1} R (UniformSpace.toTopologicalSpace.{u1} R _inst_4) (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R (Ring.toNonAssocRing.{u1} R _inst_3)))], UniformContinuousConstSMul.{u1, u1} R R _inst_4 (SMulZeroClass.toSMul.{u1, u1} R R (MonoidWithZero.toZero.{u1} R (Semiring.toMonoidWithZero.{u1} R (Ring.toSemiring.{u1} R _inst_3))) (SMulWithZero.toSMulZeroClass.{u1, u1} R R (MonoidWithZero.toZero.{u1} R (Semiring.toMonoidWithZero.{u1} R (Ring.toSemiring.{u1} R _inst_3))) (MonoidWithZero.toZero.{u1} R (Semiring.toMonoidWithZero.{u1} R (Ring.toSemiring.{u1} R _inst_3))) (MulZeroClass.toSMulWithZero.{u1} R (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R (Ring.toNonAssocRing.{u1} R _inst_3)))))))
Case conversion may be inaccurate. Consider using '#align ring.has_uniform_continuous_const_smul Ring.uniformContinuousConstSMulₓ'. -/
/-- The action of `semiring.to_module` is uniformly continuous. -/
instance Ring.uniformContinuousConstSMul [Ring R] [UniformSpace R] [UniformAddGroup R]
    [ContinuousMul R] : UniformContinuousConstSMul R R :=
  uniformContinuousConstSMul_of_continuousConstSMul _ _
#align ring.has_uniform_continuous_const_smul Ring.uniformContinuousConstSMul

/- warning: ring.has_uniform_continuous_const_op_smul -> Ring.uniformContinuousConstSMul_op is a dubious translation:
lean 3 declaration is
  forall (R : Type.{u1}) [_inst_3 : Ring.{u1} R] [_inst_4 : UniformSpace.{u1} R] [_inst_5 : UniformAddGroup.{u1} R _inst_4 (AddGroupWithOne.toAddGroup.{u1} R (AddCommGroupWithOne.toAddGroupWithOne.{u1} R (Ring.toAddCommGroupWithOne.{u1} R _inst_3)))] [_inst_6 : ContinuousMul.{u1} R (UniformSpace.toTopologicalSpace.{u1} R _inst_4) (Distrib.toHasMul.{u1} R (Ring.toDistrib.{u1} R _inst_3))], UniformContinuousConstSMul.{u1, u1} (MulOpposite.{u1} R) R _inst_4 (Mul.toHasOppositeSMul.{u1} R (Distrib.toHasMul.{u1} R (Ring.toDistrib.{u1} R _inst_3)))
but is expected to have type
  forall (R : Type.{u1}) [_inst_3 : Ring.{u1} R] [_inst_4 : UniformSpace.{u1} R] [_inst_5 : UniformAddGroup.{u1} R _inst_4 (AddGroupWithOne.toAddGroup.{u1} R (Ring.toAddGroupWithOne.{u1} R _inst_3))] [_inst_6 : ContinuousMul.{u1} R (UniformSpace.toTopologicalSpace.{u1} R _inst_4) (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R (Ring.toNonAssocRing.{u1} R _inst_3)))], UniformContinuousConstSMul.{u1, u1} (MulOpposite.{u1} R) R _inst_4 (Mul.toHasOppositeSMul.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R (Ring.toNonAssocRing.{u1} R _inst_3))))
Case conversion may be inaccurate. Consider using '#align ring.has_uniform_continuous_const_op_smul Ring.uniformContinuousConstSMul_opₓ'. -/
/-- The action of `semiring.to_opposite_module` is uniformly continuous. -/
instance Ring.uniformContinuousConstSMul_op [Ring R] [UniformSpace R] [UniformAddGroup R]
    [ContinuousMul R] : UniformContinuousConstSMul Rᵐᵒᵖ R :=
  uniformContinuousConstSMul_of_continuousConstSMul _ _
#align ring.has_uniform_continuous_const_op_smul Ring.uniformContinuousConstSMul_op

section SMul

variable [SMul M X]

#print UniformContinuousConstSMul.to_continuousConstSMul /-
@[to_additive]
instance (priority := 100) UniformContinuousConstSMul.to_continuousConstSMul
    [UniformContinuousConstSMul M X] : ContinuousConstSMul M X :=
  ⟨fun c => (uniformContinuous_const_smul c).Continuous⟩
#align has_uniform_continuous_const_smul.to_has_continuous_const_smul UniformContinuousConstSMul.to_continuousConstSMul
#align has_uniform_continuous_const_vadd.to_has_continuous_const_vadd UniformContinuousConstVAdd.to_continuousConstVAdd
-/

variable {M X Y}

#print UniformContinuous.const_smul /-
@[to_additive]
theorem UniformContinuous.const_smul [UniformContinuousConstSMul M X] {f : Y → X}
    (hf : UniformContinuous f) (c : M) : UniformContinuous (c • f) :=
  (uniformContinuous_const_smul c).comp hf
#align uniform_continuous.const_smul UniformContinuous.const_smul
#align uniform_continuous.const_vadd UniformContinuous.const_vadd
-/

#print UniformContinuousConstSMul.op /-
/-- If a scalar action is central, then its right action is uniform continuous when its left action
is. -/
@[to_additive
      "If an additive action is central, then its right action is uniform\ncontinuous when its left action,is."]
instance (priority := 100) UniformContinuousConstSMul.op [SMul Mᵐᵒᵖ X] [IsCentralScalar M X]
    [UniformContinuousConstSMul M X] : UniformContinuousConstSMul Mᵐᵒᵖ X :=
  ⟨MulOpposite.rec' fun c =>
      by
      change UniformContinuous fun m => MulOpposite.op c • m
      simp_rw [op_smul_eq_smul]
      exact uniform_continuous_const_smul c⟩
#align has_uniform_continuous_const_smul.op UniformContinuousConstSMul.op
#align has_uniform_continuous_const_vadd.op UniformContinuousConstVAdd.op
-/

#print MulOpposite.uniformContinuousConstSMul /-
@[to_additive]
instance MulOpposite.uniformContinuousConstSMul [UniformContinuousConstSMul M X] :
    UniformContinuousConstSMul M Xᵐᵒᵖ :=
  ⟨fun c =>
    MulOpposite.uniformContinuous_op.comp <| MulOpposite.uniformContinuous_unop.const_smul c⟩
#align mul_opposite.has_uniform_continuous_const_smul MulOpposite.uniformContinuousConstSMul
#align add_opposite.has_uniform_continuous_const_vadd AddOpposite.uniformContinuousConstVAdd
-/

end SMul

/- warning: uniform_group.to_has_uniform_continuous_const_smul -> UniformGroup.to_uniformContinuousConstSMul is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} [_inst_3 : Group.{u1} G] [_inst_4 : UniformSpace.{u1} G] [_inst_5 : UniformGroup.{u1} G _inst_4 _inst_3], UniformContinuousConstSMul.{u1, u1} G G _inst_4 (Mul.toSMul.{u1} G (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_3)))))
but is expected to have type
  forall {G : Type.{u1}} [_inst_3 : Group.{u1} G] [_inst_4 : UniformSpace.{u1} G] [_inst_5 : UniformGroup.{u1} G _inst_4 _inst_3], UniformContinuousConstSMul.{u1, u1} G G _inst_4 (MulAction.toSMul.{u1, u1} G G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_3)) (Monoid.toMulAction.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_3))))
Case conversion may be inaccurate. Consider using '#align uniform_group.to_has_uniform_continuous_const_smul UniformGroup.to_uniformContinuousConstSMulₓ'. -/
@[to_additive]
instance UniformGroup.to_uniformContinuousConstSMul {G : Type u} [Group G] [UniformSpace G]
    [UniformGroup G] : UniformContinuousConstSMul G G :=
  ⟨fun c => uniformContinuous_const.mul uniformContinuous_id⟩
#align uniform_group.to_has_uniform_continuous_const_smul UniformGroup.to_uniformContinuousConstSMul
#align uniform_add_group.to_has_uniform_continuous_const_vadd UniformAddGroup.to_uniformContinuousConstVAdd

namespace UniformSpace

namespace Completion

section SMul

variable [SMul M X]

@[to_additive]
instance : SMul M (Completion X) :=
  ⟨fun c => Completion.map ((· • ·) c)⟩

#print UniformSpace.Completion.smul_def /-
@[to_additive]
theorem smul_def (c : M) (x : Completion X) : c • x = Completion.map ((· • ·) c) x :=
  rfl
#align uniform_space.completion.smul_def UniformSpace.Completion.smul_def
#align uniform_space.completion.vadd_def UniformSpace.Completion.vadd_def
-/

@[to_additive]
instance : UniformContinuousConstSMul M (Completion X) :=
  ⟨fun c => uniformContinuous_map⟩

@[to_additive]
instance [SMul N X] [SMul M N] [UniformContinuousConstSMul M X] [UniformContinuousConstSMul N X]
    [IsScalarTower M N X] : IsScalarTower M N (Completion X) :=
  ⟨fun m n x =>
    by
    have : _ = (_ : completion X → completion X) :=
      map_comp (uniform_continuous_const_smul m) (uniform_continuous_const_smul n)
    refine' Eq.trans _ (congr_fun this.symm x)
    exact congr_arg (fun f => completion.map f x) (funext (smul_assoc _ _))⟩

@[to_additive]
instance [SMul N X] [SMulCommClass M N X] [UniformContinuousConstSMul M X]
    [UniformContinuousConstSMul N X] : SMulCommClass M N (Completion X) :=
  ⟨fun m n x =>
    by
    have hmn : m • n • x = (completion.map (SMul.smul m) ∘ completion.map (SMul.smul n)) x := rfl
    have hnm : n • m • x = (completion.map (SMul.smul n) ∘ completion.map (SMul.smul m)) x := rfl
    rw [hmn, hnm, map_comp, map_comp]
    exact congr_arg (fun f => completion.map f x) (funext (smul_comm _ _))
    repeat' exact uniform_continuous_const_smul _⟩

@[to_additive]
instance [SMul Mᵐᵒᵖ X] [IsCentralScalar M X] : IsCentralScalar M (Completion X) :=
  ⟨fun c a => (congr_arg fun f => Completion.map f a) <| funext (op_smul_eq_smul c)⟩

variable {M X} [UniformContinuousConstSMul M X]

/- warning: uniform_space.completion.coe_smul -> UniformSpace.Completion.coe_smul is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {X : Type.{u2}} [_inst_1 : UniformSpace.{u2} X] [_inst_3 : SMul.{u1, u2} M X] [_inst_4 : UniformContinuousConstSMul.{u1, u2} M X _inst_1 _inst_3] (c : M) (x : X), Eq.{succ u2} (UniformSpace.Completion.{u2} X _inst_1) ((fun (a : Type.{u2}) (b : Type.{u2}) [self : HasLiftT.{succ u2, succ u2} a b] => self.0) X (UniformSpace.Completion.{u2} X _inst_1) (HasLiftT.mk.{succ u2, succ u2} X (UniformSpace.Completion.{u2} X _inst_1) (CoeTCₓ.coe.{succ u2, succ u2} X (UniformSpace.Completion.{u2} X _inst_1) (UniformSpace.Completion.hasCoeT.{u2} X _inst_1))) (SMul.smul.{u1, u2} M X _inst_3 c x)) (SMul.smul.{u1, u2} M (UniformSpace.Completion.{u2} X _inst_1) (UniformSpace.Completion.hasSmul.{u1, u2} M X _inst_1 _inst_3) c ((fun (a : Type.{u2}) (b : Type.{u2}) [self : HasLiftT.{succ u2, succ u2} a b] => self.0) X (UniformSpace.Completion.{u2} X _inst_1) (HasLiftT.mk.{succ u2, succ u2} X (UniformSpace.Completion.{u2} X _inst_1) (CoeTCₓ.coe.{succ u2, succ u2} X (UniformSpace.Completion.{u2} X _inst_1) (UniformSpace.Completion.hasCoeT.{u2} X _inst_1))) x))
but is expected to have type
  forall {M : Type.{u1}} {X : Type.{u2}} [_inst_1 : UniformSpace.{u2} X] [_inst_3 : SMul.{u1, u2} M X] [_inst_4 : UniformContinuousConstSMul.{u1, u2} M X _inst_1 _inst_3] (c : M) (x : X), Eq.{succ u2} (UniformSpace.Completion.{u2} X _inst_1) (UniformSpace.Completion.coe'.{u2} X _inst_1 (HSMul.hSMul.{u1, u2, u2} M X X (instHSMul.{u1, u2} M X _inst_3) c x)) (HSMul.hSMul.{u1, u2, u2} M (UniformSpace.Completion.{u2} X _inst_1) (UniformSpace.Completion.{u2} X _inst_1) (instHSMul.{u1, u2} M (UniformSpace.Completion.{u2} X _inst_1) (UniformSpace.Completion.instSMulCompletion.{u1, u2} M X _inst_1 _inst_3)) c (UniformSpace.Completion.coe'.{u2} X _inst_1 x))
Case conversion may be inaccurate. Consider using '#align uniform_space.completion.coe_smul UniformSpace.Completion.coe_smulₓ'. -/
@[simp, norm_cast, to_additive]
theorem coe_smul (c : M) (x : X) : ↑(c • x) = (c • x : Completion X) :=
  (map_coe (uniformContinuous_const_smul c) x).symm
#align uniform_space.completion.coe_smul UniformSpace.Completion.coe_smul
#align uniform_space.completion.coe_vadd UniformSpace.Completion.coe_vadd

end SMul

@[to_additive]
instance [Monoid M] [MulAction M X] [UniformContinuousConstSMul M X] : MulAction M (Completion X)
    where
  smul := (· • ·)
  one_smul := ext' (continuous_const_smul _) continuous_id fun a => by rw [← coe_smul, one_smul]
  mul_smul x y :=
    ext' (continuous_const_smul _) ((continuous_const_smul _).const_smul _) fun a => by
      simp only [← coe_smul, mul_smul]

end Completion

end UniformSpace


import data.set.basic

universes u v w

open set

lemma factors_iff {α : Type u} {β : Type v} {γ : Type w} (f : α → γ) (g : β → γ) :
  (∃ h, f = g ∘ h) ↔ range f ⊆ range g :=
⟨by rintros ⟨h, rfl⟩ _ ⟨a, rfl⟩; use h a,
 λ H, by choose h hh using H; exact ⟨λ a, h ⟨a, rfl⟩, funext $ λ a, (hh ⟨a, rfl⟩).symm⟩⟩


/-
Copyright (c) 2014 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Gabriel Ebner
-/
import algebra.group.basic

/-!
# Cast of natural numbers

This file defines the *canonical* homomorphism from the natural numbers into an
`add_monoid` with a one.  In additive monoids with one, there exists a unique
such homomorphism and we store it in the `nat_cast : ℕ → R` field.

Preferentially, the homomorphism is written as a coercion.

## Main declarations

* `add_monoid_with_one`: Type class for `nat.cast`.
* `nat.cast`: Canonical homomorphism `ℕ → R`.
-/

universes u
set_option old_structure_cmd true

/-- The numeral `((0+1)+⋯)+1`. -/
protected def nat.unary_cast {R : Type u} [has_one R] [has_zero R] [has_add R] : ℕ → R
| 0 := 0
| (n + 1) := nat.unary_cast n + 1

/--
Type class for the canonical homomorphism `ℕ → R`.
-/
@[protect_proj]
class has_nat_cast (R : Type u) :=
(nat_cast : ℕ → R)

/--
An `add_monoid_with_one` is an `add_monoid` with a `1`.
It also contains data for the unique homomorphism `ℕ → R`.
-/
@[protect_proj]
class add_monoid_with_one (R : Type u) extends has_nat_cast R, add_monoid R, has_one R :=
(nat_cast := nat.unary_cast)
(nat_cast_zero : nat_cast 0 = (0 : R) . control_laws_tac)
(nat_cast_succ : ∀ n, nat_cast (n + 1) = (nat_cast n + 1 : R) . control_laws_tac)

/-- Canonical homomorphism from `ℕ` to a additive monoid `R` with a `1`. -/
protected def nat.cast {R : Type u} [has_nat_cast R] : ℕ → R := has_nat_cast.nat_cast

/-- An `add_comm_monoid_with_one` is an `add_monoid_with_one` satisfying `a + b = b + a`.  -/
@[protect_proj]
class add_comm_monoid_with_one (R : Type*) extends add_monoid_with_one R, add_comm_monoid R

section
variables {R : Type*} [add_monoid_with_one R]

/--
Coercions such as `nat.cast_coe` that go from a concrete structure such as
`ℕ` to an arbitrary ring `R` should be set up as follows:
```lean
@[priority 900] instance : has_coe_t ℕ R := ⟨...⟩
```

It needs to be `has_coe_t` instead of `has_coe` because otherwise type-class
inference would loop when constructing the transitive coercion `ℕ → ℕ → ℕ → ...`.
The reduced priority is necessary so that it doesn't conflict with instances
such as `has_coe_t R (option R)`.

For this to work, we reduce the priority of the `coe_base` and `coe_trans`
instances because we want the instances for `has_coe_t` to be tried in the
following order:

 1. `has_coe_t` instances declared in mathlib (such as `has_coe_t R (with_top R)`, etc.)
 2. `coe_base`, which contains instances such as `has_coe (fin n) n`
 3. `nat.cast_coe : has_coe_t ℕ R` etc.
 4. `coe_trans`

If `coe_trans` is tried first, then `nat.cast_coe` doesn't get a chance to apply.
-/
library_note "coercion into rings"
attribute [instance, priority 950] coe_base
attribute [instance, priority 500] coe_trans

namespace nat

-- see note [coercion into rings]
@[priority 900] instance cast_coe {R} [has_nat_cast R] : has_coe_t ℕ R := ⟨nat.cast⟩

@[simp, norm_cast]

-- Lemmas about nat.succ need to get a low priority, so that they are tried last.
-- This is because `nat.succ _` matches `1`, `3`, `x+1`, etc.
-- Rewriting would then produce really wrong terms.
@[simp, norm_cast, priority 500]
theorem cast_succ (n : ℕ) : ((succ n : ℕ) : R) = n + 1 := add_monoid_with_one.nat_cast_succ _

theorem cast_add_one (n : ℕ) : ((n + 1 : ℕ) : R) = n + 1 := cast_succ _

@[simp, norm_cast] theorem cast_ite (P : Prop) [decidable P] (m n : ℕ) :
  (((ite P m n) : ℕ) : R) = ite P (m : R) (n : R) :=
by { split_ifs; refl, }

end nat

end

namespace nat
variables {R : Type*}

@[simp, norm_cast] theorem cast_one [add_monoid_with_one R] : ((1 : ℕ) : R) = 1 :=
by rw [cast_succ, cast_zero, zero_add]

@[simp, norm_cast] theorem cast_add [add_monoid_with_one R] (m n : ℕ) : ((m + n : ℕ) : R) = m + n :=
by induction n; simp [add_succ, add_assoc, nat.add_zero, *]

/-- Computationally friendlier cast than `nat.unary_cast`, using binary representation. -/
protected def bin_cast [has_zero R] [has_one R] [has_add R] (n : ℕ) : R :=
@nat.binary_rec (λ _, R) 0 (λ odd k a, cond odd (a + a + 1) (a + a)) n

@[simp] lemma bin_cast_eq [add_monoid_with_one R] (n : ℕ) : (nat.bin_cast n : R) = ((n : ℕ) : R) :=
begin
  rw nat.bin_cast,
  apply binary_rec _ _ n,
  { rw [binary_rec_zero, cast_zero] },
  { intros b k h,
    rw [binary_rec_eq, h],
    { cases b; simp [bit, bit0, bit1] },
    { simp } },
end

@[simp, norm_cast] theorem cast_bit0 [add_monoid_with_one R] (n : ℕ) :
  ((bit0 n : ℕ) : R) = bit0 n := cast_add _ _

@[simp, norm_cast] theorem cast_bit1 [add_monoid_with_one R] (n : ℕ) :
  ((bit1 n : ℕ) : R) = bit1 n :=
by rw [bit1, cast_add_one, cast_bit0]; refl

lemma cast_two [add_monoid_with_one R] : ((2 : ℕ) : R) = 2 :=
by rw [cast_add_one, cast_one, bit0]

attribute [simp, norm_cast] int.nat_abs_of_nat

end nat

/-- `add_monoid_with_one` implementation using unary recursion. -/
@[reducible] protected def add_monoid_with_one.unary {R : Type*} [add_monoid R] [has_one R] :
  add_monoid_with_one R :=
{ .. ‹has_one R›, .. ‹add_monoid R› }

/-- `add_monoid_with_one` implementation using binary recursion. -/
@[reducible] protected def add_monoid_with_one.binary {R : Type*} [add_monoid R] [has_one R] :
  add_monoid_with_one R :=
{ nat_cast := nat.bin_cast,
  nat_cast_zero := by simp [nat.bin_cast, nat.cast],
  nat_cast_succ := λ n, begin
    simp only [nat.cast],
    letI : add_monoid_with_one R := add_monoid_with_one.unary,
    erw [nat.bin_cast_eq, nat.bin_cast_eq, nat.cast_succ],
    refl,
  end,
  .. ‹has_one R›, .. ‹add_monoid R› }


import LTS property_catalogue.LTL.patterns tactic

open tactic

variable {M : LTS}

namespace precedes

namespace globally

-- Proof 1 : Because S never happens
lemma vacuous (P S : formula M) (π : path M) :
 (sat (absent.globally S) π) → sat (precedes.globally P S) π
:= λ s, or.inl s

-- Proof 2 : P precedes S because S can't happen before P

local notation π `⊨ `P := sat P π

variables (P Q R : formula M) (π : path M)

lemma by_absent_before (P Q : formula M) (π : path M) :
 (π ⊨ absent.before Q P) ∧ (π ⊨ exist.globally P ) →
 (π ⊨ precedes.globally P Q)  :=
begin
 rintros ⟨H1,H2⟩,
 rw precedes.globally,
 rw [absent.before, sat, imp_iff_not_or] at H1,
 cases H1,
 left, rw always_eventually_dual, contradiction,
 right, assumption,
end

-- Proof 3 : P precedes R because P precedes Q and Q precedes R

lemma by_transitive (P Q R : formula M) (π : path M) :
    (π ⊨ precedes.globally P R) ∧ (π ⊨ precedes.globally R Q) →
    (π ⊨ precedes.globally P Q) :=
begin
    rintros ⟨H1, H2⟩,
    cases H2, left, assumption,
    cases H1,
    cases H2 with k H2, replace H1 := (H1 k), replace H2:= H2.1,
    have := sat_em R (π.drop k),replace this := this H2,
    contradiction,
    rcases H1 with ⟨k,Hk1,Hk2⟩,
    rcases H2 with ⟨w, Hw1, Hw2⟩,
    right, use k,
    split, assumption,
    intros i Hi, apply Hw2,
    have EM : (k < w) ∨ ¬ (k < w), from em (k < w),
    cases EM,
    apply lt_trans Hi, assumption,
    simp at EM,
     have EM2 : (k = w) ∨ ¬ (k = w), from em (k = w),
    cases EM2,
    rw ← EM2, assumption,
    have : w < k, by omega,
    replace Hk2 := Hk2 w this,
    have := sat_em R (π.drop w),replace this := this Hw1,
    contradiction,
end

meta def solve_by_transitive (e₁ e₂ e₃ : expr) (s : string): tactic unit :=
do
  tactic.interactive.apply ``(by_transitive %%e₁  %%e₂  %%e₃)
 -- e₁ ← tactic_format_expr e₁,
  --  e₂ ← tactic_format_expr e₂,
 --   e₃ ← tactic_format_expr e₃,

--  return $  s.append $
 -- "apply precedes.globally.by_transitive " ++
 --  e₁.to_string  ++ " " ++ e₂.to_string  ++ " " ++ e₃.to_string  ++ ",\n"

meta def solve_by_absent_before (e₁ e₂ : expr) (s : string) :
tactic unit :=
do
  tactic.interactive.apply ``(by_absent_before %%e₁ %%e₂)
--  e₁ ← e₁.log_format,
--  e₂ ← e₂.log_format,
--  s.log $
--  "apply precedes.globally.by_absent_before " ++
--  e₁ ++ " " ++ e₂ ++ ",\n"

meta def solve (e₁  e₂ : expr) (s : string) : list expr → tactic unit
| [] :=  return ()
| (h::t) :=
do typ ← infer_type h,
   match typ with
   | `(sat (precedes.globally _ %%new) _) :=
       solve_by_transitive e₁ e₂ new s <|> solve t
   | `(sat (absent.before _ _) _) :=
      solve_by_absent_before e₁ e₂ s  <|> solve t
   | _ := solve t
end

end globally

namespace before

-- (◆R) ⇒ ((!P) U (S ⅋ R))

lemma vacuous (P R S: formula M) (π : path M) :
  sat (absent.globally S) π → sat (precedes.before P R S) π :=
by {intro H, rw [precedes.before,sat,imp_iff_not_or,
    ← always_eventually_dual], left, assumption}

lemma by_absent_before (P R S: formula M) (π : path M) :
    sat (exist.globally (P ⅋ S)) π
    ∧ sat (absent.before R (P ⅋ S)) π → sat (precedes.before P R S) π
:=
begin
    rintros ⟨L,R⟩,
    replace R := R L,
    rw [precedes.before, sat, imp_iff_not_or],
    exact or.inr R,
end

end before

namespace after
--  (◾!Q) ⅋ ◆(Q & ((!P) W S))

end after

namespace between
-- ◾((Q & (!R) & ◆R) ⇒ (!P U (S ⅋ R)))

end between
end precedes


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

import category_theory.idempotents.basic
import category_theory.preadditive.additive_functor
import category_theory.equivalence

/-!
# The Karoubi envelope of a category

In this file, we define the Karoubi envelope `karoubi C` of a category `C`.

## Main constructions and definitions

- `karoubi C` is the Karoubi envelope of a category `C`: it is an idempotent
complete category. It is also preadditive when `C` is preadditive.
- `to_karoubi C : C ⥤ karoubi C` is a fully faithful functor, which is an equivalence
(`to_karoubi_is_equivalence`) when `C` is idempotent complete.

-/

noncomputable theory

open category_theory.category
open category_theory.preadditive
open category_theory.limits
open_locale big_operators

namespace category_theory

variables (C : Type*) [category C]

namespace idempotents

/-- In a preadditive category `C`, when an object `X` decomposes as `X ≅ P ⨿ Q`, one may
consider `P` as a direct factor of `X` and up to unique isomorphism, it is determined by the
obvious idempotent `X ⟶ P ⟶ X` which is the projection onto `P` with kernel `Q`. More generally,
one may define a formal direct factor of an object `X : C` : it consists of an idempotent
`p : X ⟶ X` which is thought as the "formal image" of `p`. The type `karoubi C` shall be the
type of the objects of the karoubi enveloppe of `C`. It makes sense for any category `C`. -/
@[nolint has_inhabited_instance]
structure karoubi := (X : C) (p : X ⟶ X) (idem : p ≫ p = p)

namespace karoubi

variables {C}

@[ext]
lemma ext {P Q : karoubi C} (h_X : P.X = Q.X)
  (h_p : P.p ≫ eq_to_hom h_X = eq_to_hom h_X ≫ Q.p) : P = Q :=
begin
  cases P,
  cases Q,
  dsimp at h_X h_p,
  subst h_X,
  simpa only [true_and, eq_self_iff_true, id_comp, eq_to_hom_refl,
    heq_iff_eq, comp_id] using h_p,
end

/-- A morphism `P ⟶ Q` in the category `karoubi C` is a morphism in the underlying category
`C` which satisfies a relation, which in the preadditive case, expresses that it induces a
map between the corresponding "formal direct factors" and that it vanishes on the complement
formal direct factor. -/
@[ext]
structure hom (P Q : karoubi C) := (f : P.X ⟶ Q.X) (comm : f = P.p ≫ f ≫ Q.p)

instance [preadditive C] (P Q : karoubi C) : inhabited (hom P Q) :=
⟨⟨0, by rw [zero_comp, comp_zero]⟩⟩

@[simp]
lemma hom_ext {P Q : karoubi C} {f g : hom P Q} : f = g ↔ f.f = g.f :=
begin
  split,
  { intro h, rw h, },
  { ext, }
end

@[simp, reassoc]
lemma p_comp {P Q : karoubi C} (f : hom P Q) : P.p ≫ f.f = f.f :=
by rw [f.comm, ← assoc, P.idem]

@[simp, reassoc]
lemma comp_p {P Q : karoubi C} (f : hom P Q) : f.f ≫ Q.p = f.f :=
by rw [f.comm, assoc, assoc, Q.idem]

lemma p_comm {P Q : karoubi C} (f : hom P Q) : P.p ≫ f.f = f.f ≫ Q.p :=
by rw [p_comp, comp_p]

lemma comp_proof {P Q R : karoubi C} (g : hom Q R) (f : hom P Q) :
  f.f ≫ g.f = P.p ≫ (f.f ≫ g.f) ≫ R.p :=
by rw [assoc, comp_p, ← assoc, p_comp]

/-- The category structure on the karoubi envelope of a category. -/
instance : category (karoubi C) :=
{ hom      := karoubi.hom,
  id       := λ P, ⟨P.p, by { repeat { rw P.idem, }, }⟩,
  comp     := λ P Q R f g, ⟨f.f ≫ g.f, karoubi.comp_proof g f⟩, }

@[simp]
lemma comp {P Q R : karoubi C} (f : P ⟶ Q) (g : Q ⟶ R) :
  f ≫ g = ⟨f.f ≫ g.f, comp_proof g f⟩ := by refl

@[simp]
lemma id_eq {P : karoubi C} : 𝟙 P = ⟨P.p, by repeat { rw P.idem, }⟩ := by refl

/-- It is possible to coerce an object of `C` into an object of `karoubi C`.
See also the functor `to_karoubi`. -/
instance coe : has_coe_t C (karoubi C) := ⟨λ X, ⟨X, 𝟙 X, by rw comp_id⟩⟩

@[simp]
lemma coe_X (X : C) : (X : karoubi C).X = X := by refl

@[simp]
lemma coe_p (X : C) : (X : karoubi C).p = 𝟙 X := by refl

@[simp]
lemma eq_to_hom_f {P Q : karoubi C} (h : P = Q) :
  karoubi.hom.f (eq_to_hom h) = P.p ≫ eq_to_hom (congr_arg karoubi.X h) :=
by { subst h, simp only [eq_to_hom_refl, karoubi.id_eq, comp_id], }

end karoubi

/-- The obvious fully faithful functor `to_karoubi` sends an object `X : C` to the obvious
formal direct factor of `X` given by `𝟙 X`. -/
@[simps]
def to_karoubi : C ⥤ karoubi C :=
{ obj := λ X, ⟨X, 𝟙 X, by rw comp_id⟩,
  map := λ X Y f, ⟨f, by simp only [comp_id, id_comp]⟩ }

instance : full (to_karoubi C) :=
{ preimage := λ X Y f, f.f, }

instance : faithful (to_karoubi C) := { }

variables {C}

@[simps]
instance [preadditive C] {P Q : karoubi C} : add_comm_group (P ⟶ Q) :=
{ add := λ f g, ⟨f.f+g.f, begin
    rw [add_comp, comp_add],
    congr',
    exacts [f.comm, g.comm],
  end⟩,
  zero := ⟨0, by simp only [comp_zero, zero_comp]⟩,
  zero_add := λ f, by { ext, simp only [zero_add], },
  add_zero := λ f, by { ext, simp only [add_zero], },
  add_assoc := λ f g h', by simp only [add_assoc],
  add_comm := λ f g, by { ext, apply_rules [add_comm], },
  neg := λ f, ⟨-f.f, by simpa only [neg_comp, comp_neg, neg_inj] using f.comm⟩,
  add_left_neg := λ f, by { ext, apply_rules [add_left_neg], }, }

namespace karoubi

lemma hom_eq_zero_iff [preadditive C] {P Q : karoubi C} {f : hom P Q} : f = 0 ↔ f.f = 0 := hom_ext

/-- The map sending `f : P ⟶ Q` to `f.f : P.X ⟶ Q.X` is additive. -/
@[simps]
def inclusion_hom [preadditive C] (P Q : karoubi C) : add_monoid_hom (P ⟶ Q) (P.X ⟶ Q.X) :=
{ to_fun    := λ f, f.f,
  map_zero' := rfl,
  map_add'  := λ f g, rfl }

@[simp]
lemma sum_hom [preadditive C] {P Q : karoubi C} {α : Type*} (s : finset α) (f : α → (P ⟶ Q)) :
  (∑ x in s, f x).f = ∑ x in s, (f x).f  :=
add_monoid_hom.map_sum (inclusion_hom P Q) f s

end karoubi

/-- The category `karoubi C` is preadditive if `C` is. -/
instance [preadditive C] : preadditive (karoubi C) :=
{ hom_group := λ P Q, by apply_instance,
  add_comp' := λ P Q R f g h,
    by { ext, simp only [add_comp, quiver.hom.add_comm_group_add_f, karoubi.comp], },
  comp_add' := λ P Q R f g h,
    by { ext, simp only [comp_add, quiver.hom.add_comm_group_add_f, karoubi.comp], }, }

instance [preadditive C] : functor.additive (to_karoubi C) := { }

open karoubi

variables (C)

instance : is_idempotent_complete (karoubi C) :=
begin
  refine ⟨_⟩,
  intros P p hp,
  have hp' := hom_ext.mp hp,
  simp only [comp] at hp',
  use ⟨P.X, p.f, hp'⟩,
  use ⟨p.f, by rw [comp_p p, hp']⟩,
  use ⟨p.f, by rw [hp', p_comp p]⟩,
  split; simpa only [hom_ext] using hp',
end

instance [is_idempotent_complete C] : ess_surj (to_karoubi C) := ⟨λ P, begin
  have h : is_idempotent_complete C := infer_instance,
  rcases is_idempotent_complete.idempotents_split P.X P.p P.idem
    with ⟨Y,i,e,⟨h₁,h₂⟩⟩,
  use Y,
  exact nonempty.intro
    { hom := ⟨i, by erw [id_comp, ← h₂, ← assoc, h₁, id_comp]⟩,
      inv := ⟨e, by erw [comp_id, ← h₂, assoc, h₁, comp_id]⟩, },
end⟩

/-- If `C` is idempotent complete, the functor `to_karoubi : C ⥤ karoubi C` is an equivalence. -/
def to_karoubi_is_equivalence [is_idempotent_complete C] :
  is_equivalence (to_karoubi C) :=
equivalence.of_fully_faithfully_ess_surj (to_karoubi C)

namespace karoubi

variables {C}

/-- The split mono which appears in the factorisation `decomp_id P`. -/
@[simps]
def decomp_id_i (P : karoubi C) : P ⟶ P.X := ⟨P.p, by erw [coe_p, comp_id, P.idem]⟩

/-- The split epi which appears in the factorisation `decomp_id P`. -/
@[simps]
def decomp_id_p (P : karoubi C) : (P.X : karoubi C) ⟶ P :=
⟨P.p, by erw [coe_p, id_comp, P.idem]⟩

/-- The formal direct factor of `P.X` given by the idempotent `P.p` in the category `C`
is actually a direct factor in the category `karoubi C`. -/
lemma decomp_id (P : karoubi C) :
  𝟙 P = (decomp_id_i P) ≫ (decomp_id_p P) :=
by { ext, simp only [comp, id_eq, P.idem, decomp_id_i, decomp_id_p], }

lemma decomp_p (P : karoubi C) :
  (to_karoubi C).map P.p = (decomp_id_p P) ≫ (decomp_id_i P) :=
by { ext, simp only [comp, decomp_id_p_f, decomp_id_i_f, P.idem, to_karoubi_map_f], }

lemma decomp_id_p_to_karoubi (X : C) : decomp_id_p ((to_karoubi C).obj X) = 𝟙 _ :=
by { ext, refl, }

lemma decomp_id_i_naturality {P Q : karoubi C} (f : P ⟶ Q) : f ≫ decomp_id_i _ =
  decomp_id_i _ ≫ ⟨f.f, by erw [comp_id, id_comp]⟩ :=
by { ext, simp only [comp, decomp_id_i_f, karoubi.comp_p, karoubi.p_comp], }

lemma decomp_id_p_naturality {P Q : karoubi C} (f : P ⟶ Q) : decomp_id_p P ≫ f =
  (⟨f.f, by erw [comp_id, id_comp]⟩ : (P.X : karoubi C) ⟶ Q.X) ≫ decomp_id_p Q :=
by { ext, simp only [comp, decomp_id_p_f, karoubi.comp_p, karoubi.p_comp], }

end karoubi

end idempotents

end category_theory