import topology.instances.real
open set topological_space classical
local attribute [instance] prop_decidable

/-! # Fuzzy Set Theory -/

-- [0, 1] interval
@[reducible]
def fuzzy := subtype $ Icc (0 : ℝ) 1

universes u v
@[reducible]
def fset (α : Type u) := α → fuzzy

def fset_of {α : Type u} (p : α → fuzzy) : fset α :=
p

#check (infer_instance : has_coe fuzzy ℝ)
#check (infer_instance : linear_order fuzzy)
#check (infer_instance : topological_space fuzzy)
instance has_zero_fuzzy : has_zero fuzzy := ⟨⟨(0 : ℝ), by apply mem_Icc.2; norm_num⟩⟩
instance has_one_fuzzy : has_one fuzzy := ⟨⟨(1 : ℝ), by apply mem_Icc.2; norm_num⟩⟩

class has_none (α : Sort*) :=
  (none : α)

instance has_none_Prop : has_none Prop := ⟨false⟩
instance has_none_bool : has_none bool := ⟨ff⟩
instance has_none_fuzzy : has_none fuzzy := ⟨0⟩
instance has_none_option (α) : has_none (option α) := ⟨none⟩
instance has_none_roption (α) : has_none (roption α) := ⟨none⟩
-- instance has_none_mem (α : Type*) (β : Type*) [has_none β] : has_mem α (α → β) :=
--   ⟨λ x f, f x ≠ has_none.none⟩

-- The canonical topological space of option types, is simply
-- the coinduced topology. This should already have been defined.
instance option.topological_space (α) [t : topological_space α] : topological_space (option α) :=
  coinduced some t

/- **Relation's TODOs**
     TODO: create (or make sure already exists)
     a `has_none` typeclass so that `option α` and
     `roption α` both have an instance of has_none
     for any type α. This will be useful in order to
     declare some non-option types which have "none"
     such as `false` for `Prop`, `ff` for `bool`, and
     `0` for the `[0, 1]` real interval
     (to be used with fuzzy set theory),
     but not for the `0` of the reals, for instance,
     since it is not the same thing to say that a
     real-valued function has value `0` at a point
     and that it is not defined at the point.
     This way, functions to a `has_none` type can
     be interpreted as partial functions even when
     they are total. What this class intends to
     provide above all is an automatic generalization
     of the concept of set membership for has_none
     types, we intend to add the following instance:

     `instance (α β) [has_none β] : has_mem α (α → β)`

     We can also add an extensionality lemma for said
     functions via funext if we want to. This code
     should probably be moved to the math directory

     TODO: consider that both `observables`
     and `perfections`, to be defined in
     observables.lean, should be typeclasses as well.
     The first should be `class observable α : Prop`,
     containing an `observable.carrier : Type*`
     which `has_none` and is a `topological_space`
     (and maybe a T0 one)
     and an axiom specifying that α is equal to
     the type of functions from possible worlds
     to the carrier. `perfections` should be defined
     as:

     `def perfection {α} [observable α] (x : α)`

     And specify that x is a perfection iff
     when cast to a map between topological spaces
     it is continuous and does not map all its domain
     to `none`. We should likewise have a typeclass
     like this for merely measurable functions.
     Maybe we should call them `measureables`.
     Note that all these definitions are supposed to
     depend on an ontology `ω`.
     An alternative to doing this
     would be to generalize the whole theory to use
     observables as the primary notion rather than
     events. This would be a major change, but
     one worth considering.

     TODO: create a relations.lean module
     in which a typeclass for n-ary relations
     is created, and binary and unary (predicates)
     relations are separately defined and added to
     the typeclass. This way, many results about the
     particular cases can be proved from a general
     theory of n-ary relations, and we don't have to
     worry about extending our theories of monadic
     and dyadic relations to a general case later.
     Relations are to be defined
     **between observables**, with typed relations
     having type signatures for the types of
     observables, and untyped relations having the same
     observable type for all its arguments.
     Achieving full generality here is desirable,
     but **not an absolute priority** in the project,
     so use sorrys and TODOs extensively here.

     TODO: rename properties.lean to essences.lean,
           and only discuss there the

     TODO: Move these TODOs to their appropriate locations.
     Only delete any of the TODOs from here when all of them are
     completed.
  -/


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

! This file was ported from Lean 3 source module category_theory.triangulated.pretriangulated
! leanprover-community/mathlib commit 6876fa15e3158ff3e4a4e2af1fb6e1945c6e8803
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.CategoryTheory.Preadditive.AdditiveFunctor
import Mathbin.CategoryTheory.Shift.Basic
import Mathbin.CategoryTheory.Triangulated.Rotate

/-!
# Pretriangulated Categories

This file contains the definition of pretriangulated categories and triangulated functors
between them.

## Implementation Notes

We work under the assumption that pretriangulated categories are preadditive categories,
but not necessarily additive categories, as is assumed in some sources.

TODO: generalise this to n-angulated categories as in https://arxiv.org/abs/1006.4592
-/

noncomputable section

open CategoryTheory

open CategoryTheory.Preadditive

open CategoryTheory.Limits

universe v v₀ v₁ v₂ u u₀ u₁ u₂

namespace CategoryTheory

open Category Pretriangulated

/-
We work in a preadditive category `C` equipped with an additive shift.
-/
variable (C : Type u) [Category.{v} C] [HasZeroObject C] [HasShift C ℤ] [Preadditive C]
  [∀ n : ℤ, Functor.Additive (shiftFunctor C n)]

variable (D : Type u₂) [Category.{v₂} D] [HasZeroObject D] [HasShift D ℤ] [Preadditive D]
  [∀ n : ℤ, Functor.Additive (shiftFunctor D n)]

/- ./././Mathport/Syntax/Translate/Command.lean:388:30: infer kinds are unsupported in Lean 4: #[`distinguishedTriangles] [] -/
/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (T₂ «expr ≅ » T₁) -/
/-- A preadditive category `C` with an additive shift, and a class of "distinguished triangles"
relative to that shift is called pretriangulated if the following hold:
* Any triangle that is isomorphic to a distinguished triangle is also distinguished.
* Any triangle of the form `(X,X,0,id,0,0)` is distinguished.
* For any morphism `f : X ⟶ Y` there exists a distinguished triangle of the form `(X,Y,Z,f,g,h)`.
* The triangle `(X,Y,Z,f,g,h)` is distinguished if and only if `(Y,Z,X⟦1⟧,g,h,-f⟦1⟧)` is.
* Given a diagram:
  ```
        f       g       h
    X  ───> Y  ───> Z  ───> X⟦1⟧
    │       │                │
    │a      │b               │a⟦1⟧'
    V       V                V
    X' ───> Y' ───> Z' ───> X'⟦1⟧
        f'      g'      h'
  ```
  where the left square commutes, and whose rows are distinguished triangles,
  there exists a morphism `c : Z ⟶ Z'` such that `(a,b,c)` is a triangle morphism.

See <https://stacks.math.columbia.edu/tag/0145>
-/
class Pretriangulated where
  distinguishedTriangles : Set (Triangle C)
  isomorphic_distinguished :
    ∀ T₁ ∈ distinguished_triangles, ∀ (T₂) (_ : T₂ ≅ T₁), T₂ ∈ distinguished_triangles
  contractible_distinguished : ∀ X : C, contractibleTriangle X ∈ distinguished_triangles
  distinguished_cocone_triangle :
    ∀ (X Y : C) (f : X ⟶ Y),
      ∃ (Z : C)(g : Y ⟶ Z)(h : Z ⟶ X⟦(1 : ℤ)⟧), Triangle.mk f g h ∈ distinguished_triangles
  rotate_distinguished_triangle :
    ∀ T : Triangle C, T ∈ distinguished_triangles ↔ T.rotate ∈ distinguished_triangles
  complete_distinguished_triangle_morphism :
    ∀ (T₁ T₂ : Triangle C) (h₁ : T₁ ∈ distinguished_triangles) (h₂ : T₂ ∈ distinguished_triangles)
      (a : T₁.obj₁ ⟶ T₂.obj₁) (b : T₁.obj₂ ⟶ T₂.obj₂) (comm₁ : T₁.mor₁ ≫ b = a ≫ T₂.mor₁),
      ∃ c : T₁.obj₃ ⟶ T₂.obj₃, T₁.mor₂ ≫ c = b ≫ T₂.mor₂ ∧ T₁.mor₃ ≫ a⟦1⟧' = c ≫ T₂.mor₃
#align category_theory.pretriangulated CategoryTheory.Pretriangulated

namespace Pretriangulated

variable [hC : Pretriangulated C]

include hC

-- mathport name: «exprdist_triang »
notation:20 "dist_triang " C => distinguishedTriangles C

/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (T «expr ∈ » «exprdist_triang »(C)) -/
/-- Given any distinguished triangle `T`, then we know `T.rotate` is also distinguished.
-/
theorem rot_of_dist_triangle (T) (_ : T ∈ (dist_triang C)) : T.rotate ∈ (dist_triang C) :=
  (rotate_distinguished_triangle T).mp H
#align category_theory.pretriangulated.rot_of_dist_triangle CategoryTheory.Pretriangulated.rot_of_dist_triangle

/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (T «expr ∈ » «exprdist_triang »(C)) -/
/-- Given any distinguished triangle `T`, then we know `T.inv_rotate` is also distinguished.
-/
theorem inv_rot_of_dist_triangle (T) (_ : T ∈ (dist_triang C)) : T.invRotate ∈ (dist_triang C) :=
  (rotate_distinguished_triangle T.invRotate).mpr
    (isomorphic_distinguished T H T.invRotate.rotate (invRotCompRot.app T))
#align category_theory.pretriangulated.inv_rot_of_dist_triangle CategoryTheory.Pretriangulated.inv_rot_of_dist_triangle

/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (T «expr ∈ » «exprdist_triang »(C)) -/
/-- Given any distinguished triangle
```
      f       g       h
  X  ───> Y  ───> Z  ───> X⟦1⟧
```
the composition `f ≫ g = 0`.
See <https://stacks.math.columbia.edu/tag/0146>
-/
theorem comp_dist_triangle_mor_zero₁₂ (T) (_ : T ∈ (dist_triang C)) : T.mor₁ ≫ T.mor₂ = 0 :=
  by
  obtain ⟨c, hc⟩ :=
    complete_distinguished_triangle_morphism _ _ (contractible_distinguished T.obj₁) H (𝟙 T.obj₁)
      T.mor₁ rfl
  simpa only [contractible_triangle_mor₂, zero_comp] using hc.left.symm
#align category_theory.pretriangulated.comp_dist_triangle_mor_zero₁₂ CategoryTheory.Pretriangulated.comp_dist_triangle_mor_zero₁₂

/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (T «expr ∈ » «exprdist_triang »(C)) -/
/-- Given any distinguished triangle
```
      f       g       h
  X  ───> Y  ───> Z  ───> X⟦1⟧
```
the composition `g ≫ h = 0`.
See <https://stacks.math.columbia.edu/tag/0146>
-/
theorem comp_dist_triangle_mor_zero₂₃ (T) (_ : T ∈ (dist_triang C)) : T.mor₂ ≫ T.mor₃ = 0 :=
  comp_dist_triangle_mor_zero₁₂ C T.rotate (rot_of_dist_triangle C T H)
#align category_theory.pretriangulated.comp_dist_triangle_mor_zero₂₃ CategoryTheory.Pretriangulated.comp_dist_triangle_mor_zero₂₃

/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (T «expr ∈ » «exprdist_triang »(C)) -/
/-- Given any distinguished triangle
```
      f       g       h
  X  ───> Y  ───> Z  ───> X⟦1⟧
```
the composition `h ≫ f⟦1⟧ = 0`.
See <https://stacks.math.columbia.edu/tag/0146>
-/
theorem comp_dist_triangle_mor_zero₃₁ (T) (_ : T ∈ (dist_triang C)) :
    T.mor₃ ≫ (shiftEquiv C 1).Functor.map T.mor₁ = 0 :=
  by
  have H₂ := rot_of_dist_triangle C T.rotate (rot_of_dist_triangle C T H)
  simpa using comp_dist_triangle_mor_zero₁₂ C T.rotate.rotate H₂
#align category_theory.pretriangulated.comp_dist_triangle_mor_zero₃₁ CategoryTheory.Pretriangulated.comp_dist_triangle_mor_zero₃₁

/-
TODO: If `C` is pretriangulated with respect to a shift,
then `Cᵒᵖ` is pretriangulated with respect to the inverse shift.
-/
end Pretriangulated

end CategoryTheory


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

/-!
# Laurent Series

## Main Definitions
* Defines `laurent_series` as an abbreviation for `hahn_series ℤ`.
* Provides a coercion `power_series R` into `laurent_series R` given by
  `hahn_series.of_power_series`.
* Defines `laurent_series.power_series_part`
* Defines the localization map `laurent_series.of_power_series_localization` which evaluates to
  `hahn_series.of_power_series`.

-/

open hahn_series
open_locale big_operators classical
noncomputable theory

universe u

/-- A `laurent_series` is implemented as a `hahn_series` with value group `ℤ`. -/
abbreviation laurent_series (R : Type*) [has_zero R] := hahn_series ℤ R

variables {R : Type u}

namespace laurent_series

section semiring
variable [semiring R]

instance : has_coe (power_series R) (laurent_series R) :=
⟨hahn_series.of_power_series ℤ R⟩

lemma coe_power_series (x : power_series R) : (x : laurent_series R) =
  hahn_series.of_power_series ℤ R x := rfl

@[simp] lemma coeff_coe_power_series (x : power_series R) (n : ℕ) :
  hahn_series.coeff (x : laurent_series R) n = power_series.coeff R n x :=
by rw [← int.nat_cast_eq_coe_nat, coe_power_series, of_power_series_apply_coeff]

/-- This is a power series that can be multiplied by an integer power of `X` to give our
  Laurent series. If the Laurent series is nonzero, `power_series_part` has a nonzero
  constant term.  -/
def power_series_part (x : laurent_series R) : power_series R :=
power_series.mk (λ n, x.coeff (x.order + n))

@[simp] lemma power_series_part_coeff (x : laurent_series R) (n : ℕ) :
  power_series.coeff R n x.power_series_part = x.coeff (x.order + n) :=
power_series.coeff_mk _ _

@[simp] lemma power_series_part_zero : power_series_part (0 : laurent_series R) = 0 :=
by { ext, simp }

@[simp] lemma power_series_part_eq_zero (x : laurent_series R) :
  x.power_series_part = 0 ↔ x = 0 :=
begin
  split,
  { contrapose!,
    intro h,
    rw [power_series.ext_iff, not_forall],
    refine ⟨0, _⟩,
    simp [coeff_order_ne_zero h] },
  { rintro rfl,
    simp }
end

@[simp] lemma single_order_mul_power_series_part (x : laurent_series R) :
  (single x.order 1 : laurent_series R) * x.power_series_part = x :=
begin
  ext n,
  rw [← sub_add_cancel n x.order, single_mul_coeff_add, sub_add_cancel, one_mul],
  by_cases h : x.order ≤ n,
  { rw [int.eq_nat_abs_of_zero_le (sub_nonneg_of_le h), coeff_coe_power_series,
      power_series_part_coeff, ← int.eq_nat_abs_of_zero_le (sub_nonneg_of_le h),
      add_sub_cancel'_right] },
  { rw [coe_power_series, of_power_series_apply, emb_domain_notin_range],
    { contrapose! h,
      exact order_le_of_coeff_ne_zero h.symm },
    { contrapose! h,
      simp only [set.mem_range, rel_embedding.coe_fn_mk, function.embedding.coe_fn_mk,
        int.nat_cast_eq_coe_nat] at h,
      obtain ⟨m, hm⟩ := h,
      rw [← sub_nonneg, ← hm],
      exact int.zero_le_of_nat _ } }
end

lemma of_power_series_power_series_part (x : laurent_series R) :
  of_power_series ℤ R x.power_series_part = single (-x.order) 1 * x :=
begin
  refine eq.trans _ (congr rfl x.single_order_mul_power_series_part),
  rw [← mul_assoc, single_mul_single, neg_add_self, mul_one, ← C_apply, C_one, one_mul,
    coe_power_series],
end

end semiring

instance [comm_semiring R] : algebra (power_series R) (laurent_series R) :=
(hahn_series.of_power_series ℤ R).to_algebra

@[simp] lemma coe_algebra_map [comm_semiring R] :
  ⇑(algebra_map (power_series R) (laurent_series R)) = hahn_series.of_power_series ℤ R :=
rfl

/-- The localization map from power series to Laurent series. -/
@[simps] instance of_power_series_localization [comm_ring R] :
  is_localization (submonoid.powers (power_series.X : power_series R)) (laurent_series R) :=
{ map_units := (begin rintro ⟨_, n, rfl⟩,
    refine ⟨⟨single (n : ℤ) 1, single (-n : ℤ) 1, _, _⟩, _⟩,
    { simp only [single_mul_single, mul_one, add_right_neg],
      refl },
    { simp only [single_mul_single, mul_one, add_left_neg],
      refl },
    { simp } end),
 surj := (begin intro z,
    by_cases h : 0 ≤ z.order,
    { refine ⟨⟨power_series.X ^ (int.nat_abs z.order) * power_series_part z, 1⟩, _⟩,
      simp only [ring_hom.map_one, mul_one, ring_hom.map_mul, coe_algebra_map,
        of_power_series_X_pow, submonoid.coe_one, int.nat_cast_eq_coe_nat],
      rw [int.nat_abs_of_nonneg h, ← coe_power_series, single_order_mul_power_series_part] },
    { refine ⟨⟨power_series_part z, power_series.X ^ (int.nat_abs z.order), ⟨_, rfl⟩⟩, _⟩,
      simp only [coe_algebra_map, of_power_series_power_series_part],
      rw [mul_comm _ z],
      refine congr rfl _,
      rw [subtype.coe_mk, of_power_series_X_pow,
          int.nat_cast_eq_coe_nat, int.of_nat_nat_abs_of_nonpos],
      exact le_of_not_ge h } end),
  eq_iff_exists := (begin intros x y,
    rw [coe_algebra_map, of_power_series_injective.eq_iff],
    split,
    { rintro rfl,
      exact ⟨1, rfl⟩ },
    { rintro ⟨⟨_, n, rfl⟩, hc⟩,
      rw [← sub_eq_zero, ← sub_mul, power_series.ext_iff] at hc,
      rw [← sub_eq_zero, power_series.ext_iff],
      intro m,
      have h := hc (m + n),
      rw [linear_map.map_zero, subtype.coe_mk, power_series.X_pow_eq, power_series.monomial,
        power_series.coeff, finsupp.single_add, mv_power_series.coeff_add_mul_monomial,
        mul_one] at h,
      exact h } end) }

instance {K : Type u} [field K] : is_fraction_ring (power_series K) (laurent_series K) :=
is_localization.of_le (submonoid.powers (power_series.X : power_series K)) _
  (powers_le_non_zero_divisors_of_no_zero_divisors power_series.X_ne_zero)
  (λ f hf, is_unit_of_mem_non_zero_divisors $ map_mem_non_zero_divisors _
    hahn_series.of_power_series_injective hf)

end laurent_series

namespace power_series

open laurent_series

variables [semiring R] (f g : power_series R)

@[simp, norm_cast] lemma coe_zero : ((0 : power_series R) : laurent_series R) = 0 :=
(of_power_series ℤ R).map_zero

@[simp, norm_cast] lemma coe_one : ((1 : power_series R) : laurent_series R) = 1 :=
(of_power_series ℤ R).map_one

@[simp, norm_cast] lemma coe_add : ((f + g : power_series R) : laurent_series R) = f + g :=
(of_power_series ℤ R).map_add _ _

@[simp, norm_cast] lemma coe_mul : ((f * g : power_series R) : laurent_series R) = f * g :=
(of_power_series ℤ R).map_mul _ _

lemma coeff_coe (i : ℤ) :
  ((f : power_series R) : laurent_series R).coeff i =
    if i < 0 then 0 else power_series.coeff R i.nat_abs f :=
begin
  cases i,
  { rw [int.nat_abs_of_nat_core, int.of_nat_eq_coe, coeff_coe_power_series,
        if_neg (int.coe_nat_nonneg _).not_lt] },
  { rw [coe_power_series, of_power_series_apply, emb_domain_notin_image_support,
        if_pos (int.neg_succ_lt_zero _)],
    simp only [not_exists, rel_embedding.coe_fn_mk, set.mem_image, not_and,
               function.embedding.coe_fn_mk, ne.def, to_power_series_symm_apply_coeff, mem_support,
               int.nat_cast_eq_coe_nat, int.coe_nat_eq, implies_true_iff, not_false_iff] }
end

@[simp, norm_cast] lemma coe_C (r : R) : ((C R r : power_series R) : laurent_series R) =
  hahn_series.C r :=
of_power_series_C _

@[simp] lemma coe_X : ((X : power_series R) : laurent_series R) = single 1 1 :=
of_power_series_X

@[simp, norm_cast] lemma coe_smul {S : Type*} [semiring S] [module R S]
  (r : R) (x : power_series S) : ((r • x : power_series S) : laurent_series S) = r • x :=
by { ext, simp [coeff_coe, coeff_smul, smul_ite] }

@[simp, norm_cast] lemma coe_bit0 :
  ((bit0 f : power_series R) : laurent_series R) = bit0 f :=
(of_power_series ℤ R).map_bit0 _

@[simp, norm_cast] lemma coe_bit1 :
  ((bit1 f : power_series R) : laurent_series R) = bit1 f :=
(of_power_series ℤ R).map_bit1 _

@[simp, norm_cast] lemma coe_pow (n : ℕ) :
  ((f ^ n : power_series R) : laurent_series R) = f ^ n :=
(of_power_series ℤ R).map_pow _ _

end power_series


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

! This file was ported from Lean 3 source module topology.covering
! leanprover-community/mathlib commit b8c810e2aac4a30bf8cda1e1c38d4f2e6065b2e7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Topology.IsLocallyHomeomorph
import Mathlib.Topology.FiberBundle.Basic

/-!
# Covering Maps

This file defines covering maps.

## Main definitions

* `IsEvenlyCovered f x I`: A point `x` is evenly covered by `f : E → X` with fiber `I` if `I` is
  discrete and there is a `Trivialization` of `f` at `x` with fiber `I`.
* `IsCoveringMap f`: A function `f : E → X` is a covering map if every point `x` is evenly
  covered by `f` with fiber `f ⁻¹' {x}`. The fibers `f ⁻¹' {x}` must be discrete, but if `X` is
  not connected, then the fibers `f ⁻¹' {x}` are not necessarily isomorphic. Also, `f` is not
  assumed to be surjective, so the fibers are even allowed to be empty.
-/

open Bundle

variable {E X : Type _} [TopologicalSpace E] [TopologicalSpace X] (f : E → X) (s : Set X)

/-- A point `x : X` is evenly covered by `f : E → X` if `x` has an evenly covered neighborhood. -/
def IsEvenlyCovered (x : X) (I : Type _) [TopologicalSpace I] :=
  DiscreteTopology I ∧ ∃ t : Trivialization I f, x ∈ t.baseSet
#align is_evenly_covered IsEvenlyCovered

namespace IsEvenlyCovered

variable {f}

/-- If `x` is evenly covered by `f`, then we can construct a trivialization of `f` at `x`. -/
noncomputable def toTrivialization {x : X} {I : Type _} [TopologicalSpace I]
    (h : IsEvenlyCovered f x I) : Trivialization (f ⁻¹' {x}) f :=
  (Classical.choose h.2).transFiberHomeomorph
    ((Classical.choose h.2).preimageSingletonHomeomorph (Classical.choose_spec h.2)).symm
#align is_evenly_covered.to_trivialization IsEvenlyCovered.toTrivialization

theorem mem_toTrivialization_baseSet {x : X} {I : Type _} [TopologicalSpace I]
    (h : IsEvenlyCovered f x I) : x ∈ h.toTrivialization.baseSet :=
  Classical.choose_spec h.2
#align is_evenly_covered.mem_to_trivialization_base_set IsEvenlyCovered.mem_toTrivialization_baseSet

theorem toTrivialization_apply {x : E} {I : Type _} [TopologicalSpace I]
    (h : IsEvenlyCovered f (f x) I) : (h.toTrivialization x).2 = ⟨x, rfl⟩ :=
  let e := Classical.choose h.2
  let h := Classical.choose_spec h.2
  let he := e.mk_proj_snd' h
  Subtype.ext
    ((e.toLocalEquiv.eq_symm_apply (e.mem_source.mpr h)
            (by rwa [he, e.mem_target, e.coe_fst (e.mem_source.mpr h)])).mpr
        he.symm).symm
#align is_evenly_covered.to_trivialization_apply IsEvenlyCovered.toTrivialization_apply

protected theorem continuousAt {x : E} {I : Type _} [TopologicalSpace I]
    (h : IsEvenlyCovered f (f x) I) : ContinuousAt f x :=
  let e := h.toTrivialization
  e.continuousAt_proj (e.mem_source.mpr (mem_toTrivialization_baseSet h))
#align is_evenly_covered.continuous_at IsEvenlyCovered.continuousAt

end IsEvenlyCovered

/-- A covering map is a continuous function `f : E → X` with discrete fibers such that each point
  of `X` has an evenly covered neighborhood. -/
def IsCoveringMapOn :=
  ∀ x ∈ s, IsEvenlyCovered f x (f ⁻¹' {x})
#align is_covering_map_on IsCoveringMapOn

namespace IsCoveringMapOn

theorem mk (F : X → Type _) [∀ x, TopologicalSpace (F x)] [hF : ∀ x, DiscreteTopology (F x)]
    (e : ∀ x ∈ s, Trivialization (F x) f) (h : ∀ (x : X) (hx : x ∈ s), x ∈ (e x hx).baseSet) :
    IsCoveringMapOn f s := fun x hx =>
  IsEvenlyCovered.to_isEvenlyCovered_preimage ⟨hF x, e x hx, h x hx⟩
#align is_covering_map_on.mk IsCoveringMapOn.mk

variable {f} {s}

protected theorem continuousAt (hf : IsCoveringMapOn f s) {x : E} (hx : f x ∈ s) :
    ContinuousAt f x :=
  (hf (f x) hx).continuousAt
#align is_covering_map_on.continuous_at IsCoveringMapOn.continuousAt

protected theorem continuousOn (hf : IsCoveringMapOn f s) : ContinuousOn f (f ⁻¹' s) :=
  ContinuousAt.continuousOn fun _ => hf.continuousAt
#align is_covering_map_on.continuous_on IsCoveringMapOn.continuousOn

protected theorem isLocallyHomeomorphOn (hf : IsCoveringMapOn f s) :
    IsLocallyHomeomorphOn f (f ⁻¹' s) := by
  refine' IsLocallyHomeomorphOn.mk f (f ⁻¹' s) fun x hx => _
  let e := (hf (f x) hx).toTrivialization
  have h := (hf (f x) hx).mem_toTrivialization_baseSet
  let he := e.mem_source.2 h
  refine'
    ⟨e.toLocalHomeomorph.trans
        { toFun := fun p => p.1
          invFun := fun p => ⟨p, x, rfl⟩
          source := e.baseSet ×ˢ ({⟨x, rfl⟩} : Set (f ⁻¹' {f x}))
          target := e.baseSet
          open_source :=
            e.open_baseSet.prod (singletons_open_iff_discrete.2 (hf (f x) hx).1 ⟨x, rfl⟩)
          open_target := e.open_baseSet
          map_source' := fun p => And.left
          map_target' := fun p hp => ⟨hp, rfl⟩
          left_inv' := fun p hp => Prod.ext rfl hp.2.symm
          right_inv' := fun p _ => rfl
          continuous_toFun := continuous_fst.continuousOn
          continuous_invFun := (continuous_id'.prod_mk continuous_const).continuousOn },
      ⟨he, by rwa [e.toLocalHomeomorph.symm_symm, e.proj_toFun x he],
        (hf (f x) hx).toTrivialization_apply⟩,
      fun p h => (e.proj_toFun p h.1).symm⟩
#align is_covering_map_on.is_locally_homeomorph_on IsCoveringMapOn.isLocallyHomeomorphOn

end IsCoveringMapOn

/-- A covering map is a continuous function `f : E → X` with discrete fibers such that each point
  of `X` has an evenly covered neighborhood. -/
def IsCoveringMap :=
  ∀ x, IsEvenlyCovered f x (f ⁻¹' {x})
#align is_covering_map IsCoveringMap

variable {f}

theorem isCoveringMap_iff_isCoveringMapOn_univ : IsCoveringMap f ↔ IsCoveringMapOn f Set.univ := by
  simp only [IsCoveringMap, IsCoveringMapOn, Set.mem_univ, forall_true_left]
#align is_covering_map_iff_is_covering_map_on_univ isCoveringMap_iff_isCoveringMapOn_univ

protected theorem IsCoveringMap.isCoveringMapOn (hf : IsCoveringMap f) :
    IsCoveringMapOn f Set.univ :=
  isCoveringMap_iff_isCoveringMapOn_univ.mp hf
#align is_covering_map.is_covering_map_on IsCoveringMap.isCoveringMapOn

variable (f)

namespace IsCoveringMap

theorem mk (F : X → Type _) [∀ x, TopologicalSpace (F x)] [∀ x, DiscreteTopology (F x)]
    (e : ∀ x, Trivialization (F x) f) (h : ∀ x, x ∈ (e x).baseSet) : IsCoveringMap f :=
  isCoveringMap_iff_isCoveringMapOn_univ.mpr
    (IsCoveringMapOn.mk f Set.univ F (fun x _ => e x) fun x _ => h x)
#align is_covering_map.mk IsCoveringMap.mk

variable {f}

protected theorem continuous (hf : IsCoveringMap f) : Continuous f :=
  continuous_iff_continuousOn_univ.mpr hf.isCoveringMapOn.continuousOn
#align is_covering_map.continuous IsCoveringMap.continuous

protected theorem isLocallyHomeomorph (hf : IsCoveringMap f) : IsLocallyHomeomorph f :=
  isLocallyHomeomorph_iff_isLocallyHomeomorphOn_univ.mpr hf.isCoveringMapOn.isLocallyHomeomorphOn
#align is_covering_map.is_locally_homeomorph IsCoveringMap.isLocallyHomeomorph

protected theorem isOpenMap (hf : IsCoveringMap f) : IsOpenMap f :=
  hf.isLocallyHomeomorph.isOpenMap
#align is_covering_map.is_open_map IsCoveringMap.isOpenMap

protected theorem quotientMap (hf : IsCoveringMap f) (hf' : Function.Surjective f) :
    QuotientMap f :=
  hf.isOpenMap.to_quotientMap hf.continuous hf'
#align is_covering_map.quotient_map IsCoveringMap.quotientMap

end IsCoveringMap

variable {f}

protected theorem IsFiberBundle.isCoveringMap {F : Type _} [TopologicalSpace F] [DiscreteTopology F]
    (hf : ∀ x : X, ∃ e : Trivialization F f, x ∈ e.baseSet) : IsCoveringMap f :=
  IsCoveringMap.mk f (fun _ => F) (fun x => Classical.choose (hf x)) fun x =>
    Classical.choose_spec (hf x)
#align is_fiber_bundle.is_covering_map IsFiberBundle.isCoveringMap

protected theorem FiberBundle.isCoveringMap {F : Type _} {E : X → Type _} [TopologicalSpace F]
    [DiscreteTopology F] [TopologicalSpace (Bundle.TotalSpace E)] [∀ x, TopologicalSpace (E x)]
    [FiberBundle F E] : IsCoveringMap (π E) :=
  IsFiberBundle.isCoveringMap fun x => ⟨trivializationAt F E x, mem_baseSet_trivializationAt F E x⟩
#align fiber_bundle.is_covering_map FiberBundle.isCoveringMap


/-
Copyright (c) 2018 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Jeremy Avigad, Mario Carneiro, Simon Hudon
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.pfunctor.univariate.default
import Mathlib.data.pfunctor.multivariate.basic
import Mathlib.PostPort

universes u l u_1

namespace Mathlib

/-!
# The M construction as a multivariate polynomial functor.

M types are potentially infinite tree-like structures. They are defined
as the greatest fixpoint of a polynomial functor.

## Main definitions

 * `M.mk`     - constructor
 * `M.dest`   - destructor
 * `M.corec`  - corecursor: useful for formulating infinite, productive computations
 * `M.bisim`  - bisimulation: proof technique to show the equality of infinite objects

## Implementation notes

Dual view of M-types:

 * `Mp`: polynomial functor
 * `M`: greatest fixed point of a polynomial functor

Specifically, we define the polynomial functor `Mp` as:

 * A := a possibly infinite tree-like structure without information in the nodes
 * B := given the tree-like structure `t`, `B t` is a valid path
   from the root of `t` to any given node.

As a result `Mp.obj α` is made of a dataless tree and a function from
its valid paths to values of `α`

The difference with the polynomial functor of an initial algebra is
that `A` is a possibly infinite tree.

## Reference

 * [Jeremy Avigad, Mario M. Carneiro and Simon Hudon, *Data Types as Quotients of Polynomial Functors*][avigad-carneiro-hudon2019]
-/

namespace mvpfunctor

/-- A path from the root of a tree to one of its node -/
inductive M.path {n : ℕ} (P : mvpfunctor (n + 1)) : pfunctor.M (last P) → fin2 n → Type u where
| root :
    (x : pfunctor.M (last P)) →
      (a : A P) →
        (f : pfunctor.B (last P) a → pfunctor.M (last P)) →
          pfunctor.M.dest x = sigma.mk a f → (i : fin2 n) → B (drop P) a i → M.path P x i
| child :
    (x : pfunctor.M (last P)) →
      (a : A P) →
        (f : pfunctor.B (last P) a → pfunctor.M (last P)) →
          pfunctor.M.dest x = sigma.mk a f →
            (j : pfunctor.B (last P) a) → (i : fin2 n) → M.path P (f j) i → M.path P x i

protected instance M.path.inhabited {n : ℕ} (P : mvpfunctor (n + 1)) (x : pfunctor.M (last P))
    {i : fin2 n} [Inhabited (B (drop P) (pfunctor.M.head x) i)] : Inhabited (M.path P x i) :=
  { default := M.path.root x (pfunctor.M.head x) (pfunctor.M.children x) sorry i Inhabited.default }

/-- Polynomial functor of the M-type of `P`. `A` is a data-less
possibly infinite tree whereas, for a given `a : A`, `B a` is a valid
path in tree `a` so that `Wp.obj α` is made of a tree and a function
from its valid paths to the values it contains -/
def Mp {n : ℕ} (P : mvpfunctor (n + 1)) : mvpfunctor n := mk (pfunctor.M (last P)) (M.path P)

/-- `n`-ary M-type for `P` -/
def M {n : ℕ} (P : mvpfunctor (n + 1)) (α : typevec n) := obj (Mp P) α

protected instance mvfunctor_M {n : ℕ} (P : mvpfunctor (n + 1)) : mvfunctor (M P) :=
  id (obj.mvfunctor (Mp P))

protected instance inhabited_M {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n}
    [I : Inhabited (A P)] [(i : fin2 n) → Inhabited (α i)] : Inhabited (M P α) :=
  obj.inhabited (Mp P)

/-- construct through corecursion the shape of an M-type
without its contents -/
def M.corec_shape {n : ℕ} (P : mvpfunctor (n + 1)) {β : Type u} (g₀ : β → A P)
    (g₂ : (b : β) → pfunctor.B (last P) (g₀ b) → β) : β → pfunctor.M (last P) :=
  pfunctor.M.corec fun (b : β) => sigma.mk (g₀ b) (g₂ b)

/-- Proof of type equality as an arrow -/
def cast_dropB {n : ℕ} (P : mvpfunctor (n + 1)) {a : A P} {a' : A P} (h : a = a') :
    typevec.arrow (B (drop P) a) (B (drop P) a') :=
  fun (i : fin2 n) (b : B (drop P) a i) => eq.rec_on h b

/-- Proof of type equality as a function -/
def cast_lastB {n : ℕ} (P : mvpfunctor (n + 1)) {a : A P} {a' : A P} (h : a = a') :
    pfunctor.B (last P) a → pfunctor.B (last P) a' :=
  fun (b : pfunctor.B (last P) a) => eq.rec_on h b

/-- Using corecursion, construct the contents of an M-type -/
def M.corec_contents {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {β : Type u} (g₀ : β → A P)
    (g₁ : (b : β) → typevec.arrow (B (drop P) (g₀ b)) α)
    (g₂ : (b : β) → pfunctor.B (last P) (g₀ b) → β) (x : pfunctor.M (last P)) (b : β) :
    x = M.corec_shape P g₀ g₂ b → typevec.arrow (M.path P x) α :=
  sorry

/-- Corecursor for M-type of `P` -/
def M.corec' {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {β : Type u} (g₀ : β → A P)
    (g₁ : (b : β) → typevec.arrow (B (drop P) (g₀ b)) α)
    (g₂ : (b : β) → pfunctor.B (last P) (g₀ b) → β) : β → M P α :=
  fun (b : β) =>
    sigma.mk (M.corec_shape P g₀ g₂ b)
      (M.corec_contents P g₀ g₁ g₂ (M.corec_shape P g₀ g₂ b) b sorry)

/-- Corecursor for M-type of `P` -/
def M.corec {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {β : Type u}
    (g : β → obj P (α ::: β)) : β → M P α :=
  M.corec' P (fun (b : β) => sigma.fst (g b)) (fun (b : β) => typevec.drop_fun (sigma.snd (g b)))
    fun (b : β) => typevec.last_fun (sigma.snd (g b))

/-- Implementation of destructor for M-type of `P` -/
def M.path_dest_left {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {x : pfunctor.M (last P)}
    {a : A P} {f : pfunctor.B (last P) a → pfunctor.M (last P)}
    (h : pfunctor.M.dest x = sigma.mk a f) (f' : typevec.arrow (M.path P x) α) :
    typevec.arrow (B (drop P) a) α :=
  fun (i : fin2 n) (c : B (drop P) a i) => f' i (M.path.root x a f h i c)

/-- Implementation of destructor for M-type of `P` -/
def M.path_dest_right {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {x : pfunctor.M (last P)}
    {a : A P} {f : pfunctor.B (last P) a → pfunctor.M (last P)}
    (h : pfunctor.M.dest x = sigma.mk a f) (f' : typevec.arrow (M.path P x) α)
    (j : pfunctor.B (last P) a) : typevec.arrow (M.path P (f j)) α :=
  fun (i : fin2 n) (c : M.path P (f j) i) => f' i (M.path.child x a f h j i c)

/-- Destructor for M-type of `P` -/
def M.dest' {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {x : pfunctor.M (last P)} {a : A P}
    {f : pfunctor.B (last P) a → pfunctor.M (last P)} (h : pfunctor.M.dest x = sigma.mk a f)
    (f' : typevec.arrow (M.path P x) α) : obj P (α ::: M P α) :=
  sigma.mk a
    (typevec.split_fun (M.path_dest_left P h f')
      fun (x_1 : typevec.last (B P a)) => sigma.mk (f x_1) (M.path_dest_right P h f' x_1))

/-- Destructor for M-types -/
def M.dest {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} (x : M P α) : obj P (α ::: M P α) :=
  M.dest' P sorry (sigma.snd x)

/-- Constructor for M-types -/
def M.mk {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} : obj P (α ::: M P α) → M P α :=
  M.corec P fun (i : obj P (α ::: M P α)) => mvfunctor.map (typevec.id ::: M.dest P) i

theorem M.dest'_eq_dest' {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {x : pfunctor.M (last P)}
    {a₁ : A P} {f₁ : pfunctor.B (last P) a₁ → pfunctor.M (last P)}
    (h₁ : pfunctor.M.dest x = sigma.mk a₁ f₁) {a₂ : A P}
    {f₂ : pfunctor.B (last P) a₂ → pfunctor.M (last P)} (h₂ : pfunctor.M.dest x = sigma.mk a₂ f₂)
    (f' : typevec.arrow (M.path P x) α) : M.dest' P h₁ f' = M.dest' P h₂ f' :=
  sorry

theorem M.dest_eq_dest' {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {x : pfunctor.M (last P)}
    {a : A P} {f : pfunctor.B (last P) a → pfunctor.M (last P)}
    (h : pfunctor.M.dest x = sigma.mk a f) (f' : typevec.arrow (M.path P x) α) :
    M.dest P (sigma.mk x f') = M.dest' P h f' :=
  M.dest'_eq_dest' P (M.dest._proof_1 P (sigma.mk x f')) h (sigma.snd (sigma.mk x f'))

theorem M.dest_corec' {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {β : Type u} (g₀ : β → A P)
    (g₁ : (b : β) → typevec.arrow (B (drop P) (g₀ b)) α)
    (g₂ : (b : β) → pfunctor.B (last P) (g₀ b) → β) (x : β) :
    M.dest P (M.corec' P g₀ g₁ g₂ x) =
        sigma.mk (g₀ x) (typevec.split_fun (g₁ x) (M.corec' P g₀ g₁ g₂ ∘ g₂ x)) :=
  rfl

theorem M.dest_corec {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {β : Type u}
    (g : β → obj P (α ::: β)) (x : β) :
    M.dest P (M.corec P g x) = mvfunctor.map (typevec.id ::: M.corec P g) (g x) :=
  sorry

theorem M.bisim_lemma {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {a₁ : A (Mp P)}
    {f₁ : typevec.arrow (B (Mp P) a₁) α} {a' : A P} {f' : typevec.arrow (typevec.drop (B P a')) α}
    {f₁' : typevec.last (B P a') → M P α}
    (e₁ : M.dest P (sigma.mk a₁ f₁) = sigma.mk a' (typevec.split_fun f' f₁')) :
    ∃ (g₁' : pfunctor.B (last P) a' → pfunctor.M (last P)),
        ∃ (e₁' : pfunctor.M.dest a₁ = sigma.mk a' g₁'),
          f' = M.path_dest_left P e₁' f₁ ∧
            f₁' =
              fun (x : pfunctor.B (last P) a') => sigma.mk (g₁' x) (M.path_dest_right P e₁' f₁ x) :=
  sorry

theorem M.bisim {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} (R : M P α → M P α → Prop)
    (h :
      ∀ (x y : M P α),
        R x y →
          ∃ (a : A P),
            ∃ (f : typevec.arrow (typevec.drop (B P a)) (typevec.drop (α ::: M P α))),
              ∃ (f₁ : typevec.last (B P a) → typevec.last (α ::: M P α)),
                ∃ (f₂ : typevec.last (B P a) → typevec.last (α ::: M P α)),
                  M.dest P x = sigma.mk a (typevec.split_fun f f₁) ∧
                    M.dest P y = sigma.mk a (typevec.split_fun f f₂) ∧
                      ∀ (i : typevec.last (B P a)), R (f₁ i) (f₂ i))
    (x : M P α) (y : M P α) (r : R x y) : x = y :=
  sorry

theorem M.bisim₀ {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} (R : M P α → M P α → Prop)
    (h₀ : equivalence R)
    (h :
      ∀ (x y : M P α),
        R x y →
          mvfunctor.map (typevec.id ::: Quot.mk R) (M.dest P x) =
            mvfunctor.map (typevec.id ::: Quot.mk R) (M.dest P y))
    (x : M P α) (y : M P α) (r : R x y) : x = y :=
  sorry

theorem M.bisim' {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} (R : M P α → M P α → Prop)
    (h :
      ∀ (x y : M P α),
        R x y →
          mvfunctor.map (typevec.id ::: Quot.mk R) (M.dest P x) =
            mvfunctor.map (typevec.id ::: Quot.mk R) (M.dest P y))
    (x : M P α) (y : M P α) (r : R x y) : x = y :=
  sorry

theorem M.dest_map {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {β : typevec n}
    (g : typevec.arrow α β) (x : M P α) :
    M.dest P (mvfunctor.map g x) =
        mvfunctor.map (g ::: fun (x : M P α) => mvfunctor.map g x) (M.dest P x) :=
  sorry

theorem M.map_dest {n : ℕ} (P : mvpfunctor (n + 1)) {α : typevec n} {β : typevec n}
    (g : typevec.arrow (α ::: M P α) (β ::: M P β)) (x : M P α)
    (h : ∀ (x : M P α), typevec.last_fun g x = mvfunctor.map (typevec.drop_fun g) x) :
    mvfunctor.map g (M.dest P x) = M.dest P (mvfunctor.map (typevec.drop_fun g) x) :=
  sorry

end Mathlib


import data.nat.prime tactic.norm_num

structure Z_p (p : ℕ) (hp : nat.prime p) :=
(f : ℕ → fin p)

namespace Z_p

variables (p : ℕ) (hp : nat.prime p)

variables {p}

theorem nat.prime.gt_zero : p > 0 := lt_of_lt_of_le (by norm_num : 0 < 2) (nat.prime.ge_two hp)

variables (p)

def pre_add_aux (n : ℕ) : fin p × bool := (⟨n % p, nat.mod_lt _ (nat.prime.gt_zero hp)⟩, n ≥ p)

variables (p hp)

def pre_add (f g : ℕ → fin p) : ℕ → (fin p × bool)
| 0            := pre_add_aux p hp ((f 0).val + (g 0).val)
| (nat.succ n) := pre_add_aux p hp ((f (n+1)).val + (g (n+1)).val + (if (pre_add n).2 then 1 else 0))

def add (f g : ℕ → fin p) : ℕ → fin p := prod.fst ∘ pre_add p hp f g

instance : has_add (Z_p p hp) := ⟨λ m n, ⟨hp, add p hp m.f n.f⟩⟩

def zero : Z_p p hp := ⟨hp, λ n, ⟨0, nat.prime.gt_zero hp⟩⟩

instance : has_zero (Z_p p hp) := ⟨zero p hp⟩

def pre_neg_aux (n : ℕ) : fin p × bool := (if h : n = 0 then (⟨0, nat.prime.gt_zero hp⟩, ff) else (⟨p-n, nat.sub_lt_self (nat.prime.gt_zero hp) (nat.pos_of_ne_zero h)⟩, tt))

def pre_neg (f : ℕ → fin p) : ℕ → (fin p × bool)
| 0            := pre_neg_aux p hp (f 0).val
| (nat.succ n) := (if (pre_neg n).2 then (⟨p - nat.succ ((f (n+1)).val), nat.sub_lt_self (nat.prime.gt_zero hp) (nat.zero_lt_succ _)⟩, tt) else pre_neg_aux p hp (f (n+1)).val)

def neg (f : ℕ → fin p) : ℕ → fin p := prod.fst ∘ pre_neg p hp f

instance : has_neg (Z_p p hp) := ⟨λ n, ⟨hp, neg p hp n.f⟩⟩

protected theorem ext : Π (m n : Z_p p hp), (∀ i, m.f i = n.f i) → m = n
| ⟨hpf, f⟩ ⟨hpg, g⟩ h := begin congr, exact funext h end

theorem zero_add (m : Z_p p hp) : m + 0 = m :=
begin
  apply Z_p.ext,
  intro i,
  unfold has_add.add,
  unfold add,
  have h : pre_add p hp m.f (0 : Z_p p hp).f i = (m.f i, ff),
    unfold has_zero.zero,
    unfold zero,
    induction i with i ih,
    unfold pre_add,
    unfold pre_add_aux,
    dsimp,
    rw nat.add_zero,
    cases m.f 0,
    congr,
    dsimp,
    exact nat.mod_eq_of_lt is_lt,
    dsimp,
    unfold to_bool,
    apply if_neg,
    exact not_le_of_lt is_lt,
    unfold pre_add at *,
    unfold pre_add_aux at *,
    dsimp at *,
    rw ih,
    dsimp,
    rw if_neg,
    rw nat.add_zero,
    rw nat.add_zero,
    cases m.f (i+1),
    congr,
    dsimp,
    exact nat.mod_eq_of_lt is_lt,
    dsimp,
    unfold to_bool,
    apply if_neg,
    exact not_le_of_lt is_lt,
    trivial,
    dsimp,
    unfold function.comp,
    rw h
end

end Z_p


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

! This file was ported from Lean 3 source module measure_theory.function.simple_func_dense_lp
! leanprover-community/mathlib commit 57ac39bd365c2f80589a700f9fbb664d3a1a30c2
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.MeasureTheory.Function.L1Space
import Mathbin.MeasureTheory.Function.SimpleFuncDense

/-!
# Density of simple functions

Show that each `Lᵖ` Borel measurable function can be approximated in `Lᵖ` norm
by a sequence of simple functions.

## Main definitions

* `measure_theory.Lp.simple_func`, the type of `Lp` simple functions
* `coe_to_Lp`, the embedding of `Lp.simple_func E p μ` into `Lp E p μ`

## Main results

* `tendsto_approx_on_univ_Lp` (Lᵖ convergence): If `E` is a `normed_add_comm_group` and `f` is
  measurable and `mem_ℒp` (for `p < ∞`), then the simple functions
  `simple_func.approx_on f hf s 0 h₀ n` may be considered as elements of `Lp E p μ`, and they tend
  in Lᵖ to `f`.
* `Lp.simple_func.dense_embedding`: the embedding `coe_to_Lp` of the `Lp` simple functions into
  `Lp` is dense.
* `Lp.simple_func.induction`, `Lp.induction`, `mem_ℒp.induction`, `integrable.induction`: to prove
  a predicate for all elements of one of these classes of functions, it suffices to check that it
  behaves correctly on simple functions.

## TODO

For `E` finite-dimensional, simple functions `α →ₛ E` are dense in L^∞ -- prove this.

## Notations

* `α →ₛ β` (local notation): the type of simple functions `α → β`.
* `α →₁ₛ[μ] E`: the type of `L1` simple functions `α → β`.
-/

noncomputable section

open Set Function Filter TopologicalSpace ENNReal Emetric Finset

open Classical Topology ENNReal MeasureTheory BigOperators

variable {α β ι E F 𝕜 : Type _}

namespace MeasureTheory

-- mathport name: «expr →ₛ »
local infixr:25 " →ₛ " => SimpleFunc

namespace SimpleFunc

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

section Lp

variable [MeasurableSpace β] [MeasurableSpace E] [NormedAddCommGroup E] [NormedAddCommGroup F]
  {q : ℝ} {p : ℝ≥0∞}

theorem nnnorm_approxOn_le [OpensMeasurableSpace E] {f : β → E} (hf : Measurable f) {s : Set E}
    {y₀ : E} (h₀ : y₀ ∈ s) [SeparableSpace s] (x : β) (n : ℕ) :
    ‖approxOn f hf s y₀ h₀ n x - f x‖₊ ≤ ‖f x - y₀‖₊ :=
  by
  have := edist_approx_on_le hf h₀ x n
  rw [edist_comm y₀] at this
  simp only [edist_nndist, nndist_eq_nnnorm] at this
  exact_mod_cast this
#align measure_theory.simple_func.nnnorm_approx_on_le MeasureTheory.SimpleFunc.nnnorm_approxOn_le

theorem norm_approxOn_y₀_le [OpensMeasurableSpace E] {f : β → E} (hf : Measurable f) {s : Set E}
    {y₀ : E} (h₀ : y₀ ∈ s) [SeparableSpace s] (x : β) (n : ℕ) :
    ‖approxOn f hf s y₀ h₀ n x - y₀‖ ≤ ‖f x - y₀‖ + ‖f x - y₀‖ :=
  by
  have := edist_approx_on_y0_le hf h₀ x n
  repeat' rw [edist_comm y₀, edist_eq_coe_nnnorm_sub] at this
  exact_mod_cast this
#align measure_theory.simple_func.norm_approx_on_y₀_le MeasureTheory.SimpleFunc.norm_approxOn_y₀_le

theorem norm_approxOn_zero_le [OpensMeasurableSpace E] {f : β → E} (hf : Measurable f) {s : Set E}
    (h₀ : (0 : E) ∈ s) [SeparableSpace s] (x : β) (n : ℕ) :
    ‖approxOn f hf s 0 h₀ n x‖ ≤ ‖f x‖ + ‖f x‖ :=
  by
  have := edist_approx_on_y0_le hf h₀ x n
  simp [edist_comm (0 : E), edist_eq_coe_nnnorm] at this
  exact_mod_cast this
#align measure_theory.simple_func.norm_approx_on_zero_le MeasureTheory.SimpleFunc.norm_approxOn_zero_le

theorem tendsto_approxOn_Lp_snorm [OpensMeasurableSpace E] {f : β → E} (hf : Measurable f)
    {s : Set E} {y₀ : E} (h₀ : y₀ ∈ s) [SeparableSpace s] (hp_ne_top : p ≠ ∞) {μ : Measure β}
    (hμ : ∀ᵐ x ∂μ, f x ∈ closure s) (hi : snorm (fun x => f x - y₀) p μ < ∞) :
    Tendsto (fun n => snorm (approxOn f hf s y₀ h₀ n - f) p μ) atTop (𝓝 0) :=
  by
  by_cases hp_zero : p = 0
  · simpa only [hp_zero, snorm_exponent_zero] using tendsto_const_nhds
  have hp : 0 < p.to_real := to_real_pos hp_zero hp_ne_top
  suffices tendsto (fun n => ∫⁻ x, ‖approx_on f hf s y₀ h₀ n x - f x‖₊ ^ p.to_real ∂μ) at_top (𝓝 0)
    by
    simp only [snorm_eq_lintegral_rpow_nnnorm hp_zero hp_ne_top]
    convert continuous_rpow_const.continuous_at.tendsto.comp this <;> simp [_root_.inv_pos.mpr hp]
  -- We simply check the conditions of the Dominated Convergence Theorem:
  -- (1) The function "`p`-th power of distance between `f` and the approximation" is measurable
  have hF_meas :
    ∀ n, Measurable fun x => (‖approx_on f hf s y₀ h₀ n x - f x‖₊ : ℝ≥0∞) ^ p.to_real := by
    simpa only [← edist_eq_coe_nnnorm_sub] using fun n =>
      (approx_on f hf s y₀ h₀ n).measurable_bind (fun y x => edist y (f x) ^ p.to_real) fun y =>
        (measurable_edist_right.comp hf).pow_const p.to_real
  -- (2) The functions "`p`-th power of distance between `f` and the approximation" are uniformly
  -- bounded, at any given point, by `λ x, ‖f x - y₀‖ ^ p.to_real`
  have h_bound :
    ∀ n,
      (fun x => (‖approx_on f hf s y₀ h₀ n x - f x‖₊ : ℝ≥0∞) ^ p.to_real) ≤ᵐ[μ] fun x =>
        ‖f x - y₀‖₊ ^ p.to_real :=
    fun n =>
    eventually_of_forall fun x =>
      rpow_le_rpow (coe_mono (nnnorm_approx_on_le hf h₀ x n)) to_real_nonneg
  -- (3) The bounding function `λ x, ‖f x - y₀‖ ^ p.to_real` has finite integral
  have h_fin : (∫⁻ a : β, ‖f a - y₀‖₊ ^ p.to_real ∂μ) ≠ ⊤ :=
    (lintegral_rpow_nnnorm_lt_top_of_snorm_lt_top hp_zero hp_ne_top hi).Ne
  -- (4) The functions "`p`-th power of distance between `f` and the approximation" tend pointwise
  -- to zero
  have h_lim :
    ∀ᵐ a : β ∂μ,
      tendsto (fun n => (‖approx_on f hf s y₀ h₀ n a - f a‖₊ : ℝ≥0∞) ^ p.to_real) at_top (𝓝 0) :=
    by
    filter_upwards [hμ]with a ha
    have : tendsto (fun n => (approx_on f hf s y₀ h₀ n) a - f a) at_top (𝓝 (f a - f a)) :=
      (tendsto_approx_on hf h₀ ha).sub tendsto_const_nhds
    convert continuous_rpow_const.continuous_at.tendsto.comp (tendsto_coe.mpr this.nnnorm)
    simp [zero_rpow_of_pos hp]
  -- Then we apply the Dominated Convergence Theorem
  simpa using tendsto_lintegral_of_dominated_convergence _ hF_meas h_bound h_fin h_lim
#align measure_theory.simple_func.tendsto_approx_on_Lp_snorm MeasureTheory.SimpleFunc.tendsto_approxOn_Lp_snorm

theorem memℒpApproxOn [BorelSpace E] {f : β → E} {μ : Measure β} (fmeas : Measurable f)
    (hf : Memℒp f p μ) {s : Set E} {y₀ : E} (h₀ : y₀ ∈ s) [SeparableSpace s]
    (hi₀ : Memℒp (fun x => y₀) p μ) (n : ℕ) : Memℒp (approxOn f fmeas s y₀ h₀ n) p μ :=
  by
  refine' ⟨(approx_on f fmeas s y₀ h₀ n).AeStronglyMeasurable, _⟩
  suffices snorm (fun x => approx_on f fmeas s y₀ h₀ n x - y₀) p μ < ⊤
    by
    have : mem_ℒp (fun x => approx_on f fmeas s y₀ h₀ n x - y₀) p μ :=
      ⟨(approx_on f fmeas s y₀ h₀ n - const β y₀).AeStronglyMeasurable, this⟩
    convert snorm_add_lt_top this hi₀
    ext x
    simp
  have hf' : mem_ℒp (fun x => ‖f x - y₀‖) p μ :=
    by
    have h_meas : Measurable fun x => ‖f x - y₀‖ :=
      by
      simp only [← dist_eq_norm]
      exact (continuous_id.dist continuous_const).Measurable.comp fmeas
    refine' ⟨h_meas.ae_measurable.ae_strongly_measurable, _⟩
    rw [snorm_norm]
    convert snorm_add_lt_top hf hi₀.neg
    ext x
    simp [sub_eq_add_neg]
  have : ∀ᵐ x ∂μ, ‖approx_on f fmeas s y₀ h₀ n x - y₀‖ ≤ ‖‖f x - y₀‖ + ‖f x - y₀‖‖ :=
    by
    refine' eventually_of_forall _
    intro x
    convert norm_approx_on_y₀_le fmeas h₀ x n
    rw [Real.norm_eq_abs, abs_of_nonneg]
    exact add_nonneg (norm_nonneg _) (norm_nonneg _)
  calc
    snorm (fun x => approx_on f fmeas s y₀ h₀ n x - y₀) p μ ≤
        snorm (fun x => ‖f x - y₀‖ + ‖f x - y₀‖) p μ :=
      snorm_mono_ae this
    _ < ⊤ := snorm_add_lt_top hf' hf'

#align measure_theory.simple_func.mem_ℒp_approx_on MeasureTheory.SimpleFunc.memℒpApproxOn

theorem tendsto_approxOn_range_Lp_snorm [BorelSpace E] {f : β → E} (hp_ne_top : p ≠ ∞)
    {μ : Measure β} (fmeas : Measurable f) [SeparableSpace (range f ∪ {0} : Set E)]
    (hf : snorm f p μ < ∞) :
    Tendsto (fun n => snorm (approxOn f fmeas (range f ∪ {0}) 0 (by simp) n - f) p μ) atTop (𝓝 0) :=
  by
  refine' tendsto_approx_on_Lp_snorm fmeas _ hp_ne_top _ _
  · apply eventually_of_forall
    intro x
    apply subset_closure
    simp
  · simpa using hf
#align measure_theory.simple_func.tendsto_approx_on_range_Lp_snorm MeasureTheory.SimpleFunc.tendsto_approxOn_range_Lp_snorm

theorem memℒpApproxOnRange [BorelSpace E] {f : β → E} {μ : Measure β} (fmeas : Measurable f)
    [SeparableSpace (range f ∪ {0} : Set E)] (hf : Memℒp f p μ) (n : ℕ) :
    Memℒp (approxOn f fmeas (range f ∪ {0}) 0 (by simp) n) p μ :=
  memℒpApproxOn fmeas hf (by simp) zeroMemℒp n
#align measure_theory.simple_func.mem_ℒp_approx_on_range MeasureTheory.SimpleFunc.memℒpApproxOnRange

theorem tendsto_approxOn_range_lp [BorelSpace E] {f : β → E} [hp : Fact (1 ≤ p)] (hp_ne_top : p ≠ ∞)
    {μ : Measure β} (fmeas : Measurable f) [SeparableSpace (range f ∪ {0} : Set E)]
    (hf : Memℒp f p μ) :
    Tendsto
      (fun n =>
        (memℒpApproxOnRange fmeas hf n).toLp (approxOn f fmeas (range f ∪ {0}) 0 (by simp) n))
      atTop (𝓝 (hf.toLp f)) :=
  by
  simpa only [Lp.tendsto_Lp_iff_tendsto_ℒp''] using
    tendsto_approx_on_range_Lp_snorm hp_ne_top fmeas hf.2
#align measure_theory.simple_func.tendsto_approx_on_range_Lp MeasureTheory.SimpleFunc.tendsto_approxOn_range_lp

end Lp

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

section Integrable

variable [MeasurableSpace β]

variable [MeasurableSpace E] [NormedAddCommGroup E]

theorem tendsto_approxOn_L1_nnnorm [OpensMeasurableSpace E] {f : β → E} (hf : Measurable f)
    {s : Set E} {y₀ : E} (h₀ : y₀ ∈ s) [SeparableSpace s] {μ : Measure β}
    (hμ : ∀ᵐ x ∂μ, f x ∈ closure s) (hi : HasFiniteIntegral (fun x => f x - y₀) μ) :
    Tendsto (fun n => ∫⁻ x, ‖approxOn f hf s y₀ h₀ n x - f x‖₊ ∂μ) atTop (𝓝 0) := by
  simpa [snorm_one_eq_lintegral_nnnorm] using
    tendsto_approx_on_Lp_snorm hf h₀ one_ne_top hμ
      (by simpa [snorm_one_eq_lintegral_nnnorm] using hi)
#align measure_theory.simple_func.tendsto_approx_on_L1_nnnorm MeasureTheory.SimpleFunc.tendsto_approxOn_L1_nnnorm

theorem integrableApproxOn [BorelSpace E] {f : β → E} {μ : Measure β} (fmeas : Measurable f)
    (hf : Integrable f μ) {s : Set E} {y₀ : E} (h₀ : y₀ ∈ s) [SeparableSpace s]
    (hi₀ : Integrable (fun x => y₀) μ) (n : ℕ) : Integrable (approxOn f fmeas s y₀ h₀ n) μ :=
  by
  rw [← mem_ℒp_one_iff_integrable] at hf hi₀⊢
  exact mem_ℒp_approx_on fmeas hf h₀ hi₀ n
#align measure_theory.simple_func.integrable_approx_on MeasureTheory.SimpleFunc.integrableApproxOn

theorem tendsto_approxOn_range_L1_nnnorm [OpensMeasurableSpace E] {f : β → E} {μ : Measure β}
    [SeparableSpace (range f ∪ {0} : Set E)] (fmeas : Measurable f) (hf : Integrable f μ) :
    Tendsto (fun n => ∫⁻ x, ‖approxOn f fmeas (range f ∪ {0}) 0 (by simp) n x - f x‖₊ ∂μ) atTop
      (𝓝 0) :=
  by
  apply tendsto_approx_on_L1_nnnorm fmeas
  · apply eventually_of_forall
    intro x
    apply subset_closure
    simp
  · simpa using hf.2
#align measure_theory.simple_func.tendsto_approx_on_range_L1_nnnorm MeasureTheory.SimpleFunc.tendsto_approxOn_range_L1_nnnorm

theorem integrableApproxOnRange [BorelSpace E] {f : β → E} {μ : Measure β} (fmeas : Measurable f)
    [SeparableSpace (range f ∪ {0} : Set E)] (hf : Integrable f μ) (n : ℕ) :
    Integrable (approxOn f fmeas (range f ∪ {0}) 0 (by simp) n) μ :=
  integrableApproxOn fmeas hf _ (integrableZero _ _ _) n
#align measure_theory.simple_func.integrable_approx_on_range MeasureTheory.SimpleFunc.integrableApproxOnRange

end Integrable

section SimpleFuncProperties

variable [MeasurableSpace α]

variable [NormedAddCommGroup E] [NormedAddCommGroup F]

variable {μ : Measure α} {p : ℝ≥0∞}

/-!
### Properties of simple functions in `Lp` spaces

A simple function `f : α →ₛ E` into a normed group `E` verifies, for a measure `μ`:
- `mem_ℒp f 0 μ` and `mem_ℒp f ∞ μ`, since `f` is a.e.-measurable and bounded,
- for `0 < p < ∞`,
  `mem_ℒp f p μ ↔ integrable f μ ↔ f.fin_meas_supp μ ↔ ∀ y ≠ 0, μ (f ⁻¹' {y}) < ∞`.
-/

theorem exists_forall_norm_le (f : α →ₛ F) : ∃ C, ∀ x, ‖f x‖ ≤ C :=
  exists_forall_le (f.map fun x => ‖x‖)
#align measure_theory.simple_func.exists_forall_norm_le MeasureTheory.SimpleFunc.exists_forall_norm_le

theorem memℒpZero (f : α →ₛ E) (μ : Measure α) : Memℒp f 0 μ :=
  memℒp_zero_iff_aeStronglyMeasurable.mpr f.AeStronglyMeasurable
#align measure_theory.simple_func.mem_ℒp_zero MeasureTheory.SimpleFunc.memℒpZero

theorem memℒpTop (f : α →ₛ E) (μ : Measure α) : Memℒp f ∞ μ :=
  let ⟨C, hfC⟩ := f.exists_forall_norm_le
  memℒpTopOfBound f.AeStronglyMeasurable C <| eventually_of_forall hfC
#align measure_theory.simple_func.mem_ℒp_top MeasureTheory.SimpleFunc.memℒpTop

protected theorem snorm'_eq {p : ℝ} (f : α →ₛ F) (μ : Measure α) :
    snorm' f p μ = (∑ y in f.range, (‖y‖₊ : ℝ≥0∞) ^ p * μ (f ⁻¹' {y})) ^ (1 / p) :=
  by
  have h_map : (fun a => (‖f a‖₊ : ℝ≥0∞) ^ p) = f.map fun a : F => (‖a‖₊ : ℝ≥0∞) ^ p := by simp
  rw [snorm', h_map, lintegral_eq_lintegral, map_lintegral]
#align measure_theory.simple_func.snorm'_eq MeasureTheory.SimpleFunc.snorm'_eq

theorem measure_preimage_lt_top_of_memℒp (hp_pos : p ≠ 0) (hp_ne_top : p ≠ ∞) (f : α →ₛ E)
    (hf : Memℒp f p μ) (y : E) (hy_ne : y ≠ 0) : μ (f ⁻¹' {y}) < ∞ :=
  by
  have hp_pos_real : 0 < p.to_real := ENNReal.toReal_pos hp_pos hp_ne_top
  have hf_snorm := mem_ℒp.snorm_lt_top hf
  rw [snorm_eq_snorm' hp_pos hp_ne_top, f.snorm'_eq, ←
    @ENNReal.lt_rpow_one_div_iff _ _ (1 / p.to_real) (by simp [hp_pos_real]),
    @ENNReal.top_rpow_of_pos (1 / (1 / p.to_real)) (by simp [hp_pos_real]),
    ENNReal.sum_lt_top_iff] at hf_snorm
  by_cases hyf : y ∈ f.range
  swap
  · suffices h_empty : f ⁻¹' {y} = ∅
    · rw [h_empty, measure_empty]
      exact ENNReal.coe_lt_top
    ext1 x
    rw [Set.mem_preimage, Set.mem_singleton_iff, mem_empty_iff_false, iff_false_iff]
    refine' fun hxy => hyf _
    rw [mem_range, Set.mem_range]
    exact ⟨x, hxy⟩
  specialize hf_snorm y hyf
  rw [ENNReal.mul_lt_top_iff] at hf_snorm
  cases hf_snorm
  · exact hf_snorm.2
  cases hf_snorm
  · refine' absurd _ hy_ne
    simpa [hp_pos_real] using hf_snorm
  · simp [hf_snorm]
#align measure_theory.simple_func.measure_preimage_lt_top_of_mem_ℒp MeasureTheory.SimpleFunc.measure_preimage_lt_top_of_memℒp

/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (y «expr ≠ » 0) -/
theorem memℒpOfFiniteMeasurePreimage (p : ℝ≥0∞) {f : α →ₛ E}
    (hf : ∀ (y) (_ : y ≠ 0), μ (f ⁻¹' {y}) < ∞) : Memℒp f p μ :=
  by
  by_cases hp0 : p = 0
  · rw [hp0, mem_ℒp_zero_iff_ae_strongly_measurable]
    exact f.ae_strongly_measurable
  by_cases hp_top : p = ∞
  · rw [hp_top]
    exact mem_ℒp_top f μ
  refine' ⟨f.ae_strongly_measurable, _⟩
  rw [snorm_eq_snorm' hp0 hp_top, f.snorm'_eq]
  refine' ENNReal.rpow_lt_top_of_nonneg (by simp) (ennreal.sum_lt_top_iff.mpr fun y hy => _).Ne
  by_cases hy0 : y = 0
  · simp [hy0, ENNReal.toReal_pos hp0 hp_top]
  · refine' ENNReal.mul_lt_top _ (hf y hy0).Ne
    exact (ENNReal.rpow_lt_top_of_nonneg ENNReal.toReal_nonneg ENNReal.coe_ne_top).Ne
#align measure_theory.simple_func.mem_ℒp_of_finite_measure_preimage MeasureTheory.SimpleFunc.memℒpOfFiniteMeasurePreimage

/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (y «expr ≠ » 0) -/
theorem memℒp_iff {f : α →ₛ E} (hp_pos : p ≠ 0) (hp_ne_top : p ≠ ∞) :
    Memℒp f p μ ↔ ∀ (y) (_ : y ≠ 0), μ (f ⁻¹' {y}) < ∞ :=
  ⟨fun h => measure_preimage_lt_top_of_memℒp hp_pos hp_ne_top f h, fun h =>
    memℒpOfFiniteMeasurePreimage p h⟩
#align measure_theory.simple_func.mem_ℒp_iff MeasureTheory.SimpleFunc.memℒp_iff

/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (y «expr ≠ » 0) -/
theorem integrable_iff {f : α →ₛ E} : Integrable f μ ↔ ∀ (y) (_ : y ≠ 0), μ (f ⁻¹' {y}) < ∞ :=
  memℒp_one_iff_integrable.symm.trans <| memℒp_iff one_ne_zero ENNReal.coe_ne_top
#align measure_theory.simple_func.integrable_iff MeasureTheory.SimpleFunc.integrable_iff

theorem memℒp_iff_integrable {f : α →ₛ E} (hp_pos : p ≠ 0) (hp_ne_top : p ≠ ∞) :
    Memℒp f p μ ↔ Integrable f μ :=
  (memℒp_iff hp_pos hp_ne_top).trans integrable_iff.symm
#align measure_theory.simple_func.mem_ℒp_iff_integrable MeasureTheory.SimpleFunc.memℒp_iff_integrable

theorem memℒp_iff_finMeasSupp {f : α →ₛ E} (hp_pos : p ≠ 0) (hp_ne_top : p ≠ ∞) :
    Memℒp f p μ ↔ f.FinMeasSupp μ :=
  (memℒp_iff hp_pos hp_ne_top).trans finMeasSupp_iff.symm
#align measure_theory.simple_func.mem_ℒp_iff_fin_meas_supp MeasureTheory.SimpleFunc.memℒp_iff_finMeasSupp

theorem integrable_iff_finMeasSupp {f : α →ₛ E} : Integrable f μ ↔ f.FinMeasSupp μ :=
  integrable_iff.trans finMeasSupp_iff.symm
#align measure_theory.simple_func.integrable_iff_fin_meas_supp MeasureTheory.SimpleFunc.integrable_iff_finMeasSupp

theorem FinMeasSupp.integrable {f : α →ₛ E} (h : f.FinMeasSupp μ) : Integrable f μ :=
  integrable_iff_finMeasSupp.2 h
#align measure_theory.simple_func.fin_meas_supp.integrable MeasureTheory.SimpleFunc.FinMeasSupp.integrable

theorem integrablePair {f : α →ₛ E} {g : α →ₛ F} :
    Integrable f μ → Integrable g μ → Integrable (pair f g) μ := by
  simpa only [integrable_iff_fin_meas_supp] using fin_meas_supp.pair
#align measure_theory.simple_func.integrable_pair MeasureTheory.SimpleFunc.integrablePair

theorem memℒpOfIsFiniteMeasure (f : α →ₛ E) (p : ℝ≥0∞) (μ : Measure α) [IsFiniteMeasure μ] :
    Memℒp f p μ :=
  let ⟨C, hfC⟩ := f.exists_forall_norm_le
  Memℒp.ofBound f.AeStronglyMeasurable C <| eventually_of_forall hfC
#align measure_theory.simple_func.mem_ℒp_of_is_finite_measure MeasureTheory.SimpleFunc.memℒpOfIsFiniteMeasure

theorem integrableOfIsFiniteMeasure [IsFiniteMeasure μ] (f : α →ₛ E) : Integrable f μ :=
  memℒp_one_iff_integrable.mp (f.memℒpOfIsFiniteMeasure 1 μ)
#align measure_theory.simple_func.integrable_of_is_finite_measure MeasureTheory.SimpleFunc.integrableOfIsFiniteMeasure

theorem measure_preimage_lt_top_of_integrable (f : α →ₛ E) (hf : Integrable f μ) {x : E}
    (hx : x ≠ 0) : μ (f ⁻¹' {x}) < ∞ :=
  integrable_iff.mp hf x hx
#align measure_theory.simple_func.measure_preimage_lt_top_of_integrable MeasureTheory.SimpleFunc.measure_preimage_lt_top_of_integrable

/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (y «expr ≠ » 0) -/
theorem measure_support_lt_top [Zero β] (f : α →ₛ β) (hf : ∀ (y) (_ : y ≠ 0), μ (f ⁻¹' {y}) < ∞) :
    μ (support f) < ∞ := by
  rw [support_eq]
  refine' (measure_bUnion_finset_le _ _).trans_lt (ennreal.sum_lt_top_iff.mpr fun y hy => _)
  rw [Finset.mem_filter] at hy
  exact hf y hy.2
#align measure_theory.simple_func.measure_support_lt_top MeasureTheory.SimpleFunc.measure_support_lt_top

theorem measure_support_lt_top_of_memℒp (f : α →ₛ E) (hf : Memℒp f p μ) (hp_ne_zero : p ≠ 0)
    (hp_ne_top : p ≠ ∞) : μ (support f) < ∞ :=
  f.measure_support_lt_top ((memℒp_iff hp_ne_zero hp_ne_top).mp hf)
#align measure_theory.simple_func.measure_support_lt_top_of_mem_ℒp MeasureTheory.SimpleFunc.measure_support_lt_top_of_memℒp

theorem measure_support_lt_top_of_integrable (f : α →ₛ E) (hf : Integrable f μ) :
    μ (support f) < ∞ :=
  f.measure_support_lt_top (integrable_iff.mp hf)
#align measure_theory.simple_func.measure_support_lt_top_of_integrable MeasureTheory.SimpleFunc.measure_support_lt_top_of_integrable

theorem measure_lt_top_of_memℒp_indicator (hp_pos : p ≠ 0) (hp_ne_top : p ≠ ∞) {c : E} (hc : c ≠ 0)
    {s : Set α} (hs : MeasurableSet s) (hcs : Memℒp ((const α c).piecewise s hs (const α 0)) p μ) :
    μ s < ⊤ :=
  by
  have : Function.support (const α c) = Set.univ := Function.support_const hc
  simpa only [mem_ℒp_iff_fin_meas_supp hp_pos hp_ne_top, fin_meas_supp_iff_support,
    support_indicator, Set.inter_univ, this] using hcs
#align measure_theory.simple_func.measure_lt_top_of_mem_ℒp_indicator MeasureTheory.SimpleFunc.measure_lt_top_of_memℒp_indicator

end SimpleFuncProperties

end SimpleFunc

/-! Construction of the space of `Lp` simple functions, and its dense embedding into `Lp`. -/

namespace Lp

open AeEqFun

variable [MeasurableSpace α] [NormedAddCommGroup E] [NormedAddCommGroup F] (p : ℝ≥0∞)
  (μ : Measure α)

variable (E)

/-- `Lp.simple_func` is a subspace of Lp consisting of equivalence classes of an integrable simple
    function. -/
def simpleFunc : AddSubgroup (lp E p μ)
    where
  carrier := { f : lp E p μ | ∃ s : α →ₛ E, (AeEqFun.mk s s.AeStronglyMeasurable : α →ₘ[μ] E) = f }
  zero_mem' := ⟨0, rfl⟩
  add_mem' := fun f g ⟨s, hs⟩ ⟨t, ht⟩ =>
    ⟨s + t, by
      simp only [← hs, ← ht, ae_eq_fun.mk_add_mk, AddSubgroup.coe_add, ae_eq_fun.mk_eq_mk,
        simple_func.coe_add]⟩
  neg_mem' := fun f ⟨s, hs⟩ =>
    ⟨-s, by
      simp only [← hs, ae_eq_fun.neg_mk, simple_func.coe_neg, ae_eq_fun.mk_eq_mk,
        AddSubgroup.coe_neg]⟩
#align measure_theory.Lp.simple_func MeasureTheory.lp.simpleFunc

variable {E p μ}

namespace SimpleFunc

section Instances

/-! Simple functions in Lp space form a `normed_space`. -/

@[norm_cast]
theorem coe_coe (f : lp.simpleFunc E p μ) : ⇑(f : lp E p μ) = f :=
  rfl
#align measure_theory.Lp.simple_func.coe_coe MeasureTheory.lp.simpleFunc.coe_coe

protected theorem eq' {f g : lp.simpleFunc E p μ} : (f : α →ₘ[μ] E) = (g : α →ₘ[μ] E) → f = g :=
  Subtype.eq ∘ Subtype.eq
#align measure_theory.Lp.simple_func.eq' MeasureTheory.lp.simpleFunc.eq'

/-! Implementation note:  If `Lp.simple_func E p μ` were defined as a `𝕜`-submodule of `Lp E p μ`,
then the next few lemmas, putting a normed `𝕜`-group structure on `Lp.simple_func E p μ`, would be
unnecessary.  But instead, `Lp.simple_func E p μ` is defined as an `add_subgroup` of `Lp E p μ`,
which does not permit this (but has the advantage of working when `E` itself is a normed group,
i.e. has no scalar action). -/

variable [NormedField 𝕜] [NormedSpace 𝕜 E]

/-- If `E` is a normed space, `Lp.simple_func E p μ` is a `has_smul`. Not declared as an
instance as it is (as of writing) used only in the construction of the Bochner integral. -/
protected def hasSmul : SMul 𝕜 (lp.simpleFunc E p μ) :=
  ⟨fun k f =>
    ⟨k • f, by
      rcases f with ⟨f, ⟨s, hs⟩⟩
      use k • s
      apply Eq.trans (ae_eq_fun.smul_mk k s s.ae_strongly_measurable).symm _
      rw [hs]
      rfl⟩⟩
#align measure_theory.Lp.simple_func.has_smul MeasureTheory.lp.simpleFunc.hasSmul

attribute [local instance] simple_func.has_smul

@[simp, norm_cast]
theorem coe_smul (c : 𝕜) (f : lp.simpleFunc E p μ) :
    ((c • f : lp.simpleFunc E p μ) : lp E p μ) = c • (f : lp E p μ) :=
  rfl
#align measure_theory.Lp.simple_func.coe_smul MeasureTheory.lp.simpleFunc.coe_smul

/-- If `E` is a normed space, `Lp.simple_func E p μ` is a module. Not declared as an
instance as it is (as of writing) used only in the construction of the Bochner integral. -/
protected def module : Module 𝕜 (lp.simpleFunc E p μ)
    where
  one_smul f := by
    ext1
    exact one_smul _ _
  mul_smul x y f := by
    ext1
    exact mul_smul _ _ _
  smul_add x f g := by
    ext1
    exact smul_add _ _ _
  smul_zero x := by
    ext1
    exact smul_zero _
  add_smul x y f := by
    ext1
    exact add_smul _ _ _
  zero_smul f := by
    ext1
    exact zero_smul _ _
#align measure_theory.Lp.simple_func.module MeasureTheory.lp.simpleFunc.module

attribute [local instance] simple_func.module

/-- If `E` is a normed space, `Lp.simple_func E p μ` is a normed space. Not declared as an
instance as it is (as of writing) used only in the construction of the Bochner integral. -/
protected def normedSpace [Fact (1 ≤ p)] : NormedSpace 𝕜 (lp.simpleFunc E p μ) :=
  ⟨fun c f => by rw [AddSubgroup.coe_norm, AddSubgroup.coe_norm, coe_smul, norm_smul]⟩
#align measure_theory.Lp.simple_func.normed_space MeasureTheory.lp.simpleFunc.normedSpace

end Instances

attribute [local instance] simple_func.module simple_func.normed_space

section ToLp

/-- Construct the equivalence class `[f]` of a simple function `f` satisfying `mem_ℒp`. -/
@[reducible]
def toLp (f : α →ₛ E) (hf : Memℒp f p μ) : lp.simpleFunc E p μ :=
  ⟨hf.toLp f, ⟨f, rfl⟩⟩
#align measure_theory.Lp.simple_func.to_Lp MeasureTheory.lp.simpleFunc.toLp

theorem toLp_eq_toLp (f : α →ₛ E) (hf : Memℒp f p μ) : (toLp f hf : lp E p μ) = hf.toLp f :=
  rfl
#align measure_theory.Lp.simple_func.to_Lp_eq_to_Lp MeasureTheory.lp.simpleFunc.toLp_eq_toLp

theorem toLp_eq_mk (f : α →ₛ E) (hf : Memℒp f p μ) :
    (toLp f hf : α →ₘ[μ] E) = AeEqFun.mk f f.AeStronglyMeasurable :=
  rfl
#align measure_theory.Lp.simple_func.to_Lp_eq_mk MeasureTheory.lp.simpleFunc.toLp_eq_mk

theorem toLp_zero : toLp (0 : α →ₛ E) zeroMemℒp = (0 : lp.simpleFunc E p μ) :=
  rfl
#align measure_theory.Lp.simple_func.to_Lp_zero MeasureTheory.lp.simpleFunc.toLp_zero

theorem toLp_add (f g : α →ₛ E) (hf : Memℒp f p μ) (hg : Memℒp g p μ) :
    toLp (f + g) (hf.add hg) = toLp f hf + toLp g hg :=
  rfl
#align measure_theory.Lp.simple_func.to_Lp_add MeasureTheory.lp.simpleFunc.toLp_add

theorem toLp_neg (f : α →ₛ E) (hf : Memℒp f p μ) : toLp (-f) hf.neg = -toLp f hf :=
  rfl
#align measure_theory.Lp.simple_func.to_Lp_neg MeasureTheory.lp.simpleFunc.toLp_neg

theorem toLp_sub (f g : α →ₛ E) (hf : Memℒp f p μ) (hg : Memℒp g p μ) :
    toLp (f - g) (hf.sub hg) = toLp f hf - toLp g hg :=
  by
  simp only [sub_eq_add_neg, ← to_Lp_neg, ← to_Lp_add]
  rfl
#align measure_theory.Lp.simple_func.to_Lp_sub MeasureTheory.lp.simpleFunc.toLp_sub

variable [NormedField 𝕜] [NormedSpace 𝕜 E]

theorem toLp_smul (f : α →ₛ E) (hf : Memℒp f p μ) (c : 𝕜) :
    toLp (c • f) (hf.const_smul c) = c • toLp f hf :=
  rfl
#align measure_theory.Lp.simple_func.to_Lp_smul MeasureTheory.lp.simpleFunc.toLp_smul

theorem norm_toLp [Fact (1 ≤ p)] (f : α →ₛ E) (hf : Memℒp f p μ) :
    ‖toLp f hf‖ = ENNReal.toReal (snorm f p μ) :=
  norm_toLp f hf
#align measure_theory.Lp.simple_func.norm_to_Lp MeasureTheory.lp.simpleFunc.norm_toLp

end ToLp

section ToSimpleFunc

/-- Find a representative of a `Lp.simple_func`. -/
def toSimpleFunc (f : lp.simpleFunc E p μ) : α →ₛ E :=
  Classical.choose f.2
#align measure_theory.Lp.simple_func.to_simple_func MeasureTheory.lp.simpleFunc.toSimpleFunc

/-- `(to_simple_func f)` is measurable. -/
@[measurability]
protected theorem measurable [MeasurableSpace E] (f : lp.simpleFunc E p μ) :
    Measurable (toSimpleFunc f) :=
  (toSimpleFunc f).Measurable
#align measure_theory.Lp.simple_func.measurable MeasureTheory.lp.simpleFunc.measurable

protected theorem stronglyMeasurable (f : lp.simpleFunc E p μ) :
    StronglyMeasurable (toSimpleFunc f) :=
  (toSimpleFunc f).StronglyMeasurable
#align measure_theory.Lp.simple_func.strongly_measurable MeasureTheory.lp.simpleFunc.stronglyMeasurable

@[measurability]
protected theorem aeMeasurable [MeasurableSpace E] (f : lp.simpleFunc E p μ) :
    AeMeasurable (toSimpleFunc f) μ :=
  (simpleFunc.measurable f).AeMeasurable
#align measure_theory.Lp.simple_func.ae_measurable MeasureTheory.lp.simpleFunc.aeMeasurable

protected theorem aeStronglyMeasurable (f : lp.simpleFunc E p μ) :
    AeStronglyMeasurable (toSimpleFunc f) μ :=
  (simpleFunc.stronglyMeasurable f).AeStronglyMeasurable
#align measure_theory.Lp.simple_func.ae_strongly_measurable MeasureTheory.lp.simpleFunc.aeStronglyMeasurable

theorem toSimpleFunc_eq_to_fun (f : lp.simpleFunc E p μ) : toSimpleFunc f =ᵐ[μ] f :=
  show ⇑(toSimpleFunc f) =ᵐ[μ] ⇑(f : α →ₘ[μ] E)
    by
    convert(ae_eq_fun.coe_fn_mk (to_simple_func f)
          (to_simple_func f).AeStronglyMeasurable).symm using
      2
    exact (Classical.choose_spec f.2).symm
#align measure_theory.Lp.simple_func.to_simple_func_eq_to_fun MeasureTheory.lp.simpleFunc.toSimpleFunc_eq_to_fun

/-- `to_simple_func f` satisfies the predicate `mem_ℒp`. -/
protected theorem memℒp (f : lp.simpleFunc E p μ) : Memℒp (toSimpleFunc f) p μ :=
  Memℒp.aeEq (toSimpleFunc_eq_to_fun f).symm <| mem_lp_iff_memℒp.mp (f : lp E p μ).2
#align measure_theory.Lp.simple_func.mem_ℒp MeasureTheory.lp.simpleFunc.memℒp

theorem toLp_toSimpleFunc (f : lp.simpleFunc E p μ) :
    toLp (toSimpleFunc f) (simpleFunc.memℒp f) = f :=
  simpleFunc.eq' (Classical.choose_spec f.2)
#align measure_theory.Lp.simple_func.to_Lp_to_simple_func MeasureTheory.lp.simpleFunc.toLp_toSimpleFunc

theorem toSimpleFunc_toLp (f : α →ₛ E) (hfi : Memℒp f p μ) : toSimpleFunc (toLp f hfi) =ᵐ[μ] f :=
  by
  rw [← ae_eq_fun.mk_eq_mk]
  exact Classical.choose_spec (to_Lp f hfi).2
#align measure_theory.Lp.simple_func.to_simple_func_to_Lp MeasureTheory.lp.simpleFunc.toSimpleFunc_toLp

variable (E μ)

theorem zero_toSimpleFunc : toSimpleFunc (0 : lp.simpleFunc E p μ) =ᵐ[μ] 0 :=
  by
  filter_upwards [to_simple_func_eq_to_fun (0 : Lp.simple_func E p μ),
    Lp.coe_fn_zero E 1 μ]with _ h₁ _
  rwa [h₁]
#align measure_theory.Lp.simple_func.zero_to_simple_func MeasureTheory.lp.simpleFunc.zero_toSimpleFunc

variable {E μ}

theorem add_toSimpleFunc (f g : lp.simpleFunc E p μ) :
    toSimpleFunc (f + g) =ᵐ[μ] toSimpleFunc f + toSimpleFunc g :=
  by
  filter_upwards [to_simple_func_eq_to_fun (f + g), to_simple_func_eq_to_fun f,
    to_simple_func_eq_to_fun g, Lp.coe_fn_add (f : Lp E p μ) g]with _
  simp only [← coe_coe, AddSubgroup.coe_add, Pi.add_apply]
  iterate 4 intro h; rw [h]
#align measure_theory.Lp.simple_func.add_to_simple_func MeasureTheory.lp.simpleFunc.add_toSimpleFunc

theorem neg_toSimpleFunc (f : lp.simpleFunc E p μ) : toSimpleFunc (-f) =ᵐ[μ] -toSimpleFunc f :=
  by
  filter_upwards [to_simple_func_eq_to_fun (-f), to_simple_func_eq_to_fun f,
    Lp.coe_fn_neg (f : Lp E p μ)]with _
  simp only [Pi.neg_apply, AddSubgroup.coe_neg, ← coe_coe]
  repeat' intro h; rw [h]
#align measure_theory.Lp.simple_func.neg_to_simple_func MeasureTheory.lp.simpleFunc.neg_toSimpleFunc

theorem sub_toSimpleFunc (f g : lp.simpleFunc E p μ) :
    toSimpleFunc (f - g) =ᵐ[μ] toSimpleFunc f - toSimpleFunc g :=
  by
  filter_upwards [to_simple_func_eq_to_fun (f - g), to_simple_func_eq_to_fun f,
    to_simple_func_eq_to_fun g, Lp.coe_fn_sub (f : Lp E p μ) g]with _
  simp only [AddSubgroup.coe_sub, Pi.sub_apply, ← coe_coe]
  repeat' intro h; rw [h]
#align measure_theory.Lp.simple_func.sub_to_simple_func MeasureTheory.lp.simpleFunc.sub_toSimpleFunc

variable [NormedField 𝕜] [NormedSpace 𝕜 E]

theorem smul_toSimpleFunc (k : 𝕜) (f : lp.simpleFunc E p μ) :
    toSimpleFunc (k • f) =ᵐ[μ] k • toSimpleFunc f :=
  by
  filter_upwards [to_simple_func_eq_to_fun (k • f), to_simple_func_eq_to_fun f,
    Lp.coe_fn_smul k (f : Lp E p μ)]with _
  simp only [Pi.smul_apply, coe_smul, ← coe_coe]
  repeat' intro h; rw [h]
#align measure_theory.Lp.simple_func.smul_to_simple_func MeasureTheory.lp.simpleFunc.smul_toSimpleFunc

theorem norm_toSimpleFunc [Fact (1 ≤ p)] (f : lp.simpleFunc E p μ) :
    ‖f‖ = ENNReal.toReal (snorm (toSimpleFunc f) p μ) := by
  simpa [to_Lp_to_simple_func] using norm_to_Lp (to_simple_func f) (simple_func.mem_ℒp f)
#align measure_theory.Lp.simple_func.norm_to_simple_func MeasureTheory.lp.simpleFunc.norm_toSimpleFunc

end ToSimpleFunc

section Induction

variable (p)

/-- The characteristic function of a finite-measure measurable set `s`, as an `Lp` simple function.
-/
def indicatorConst {s : Set α} (hs : MeasurableSet s) (hμs : μ s ≠ ∞) (c : E) :
    lp.simpleFunc E p μ :=
  toLp ((SimpleFunc.const _ c).piecewise s hs (SimpleFunc.const _ 0))
    (memℒpIndicatorConst p hs c (Or.inr hμs))
#align measure_theory.Lp.simple_func.indicator_const MeasureTheory.lp.simpleFunc.indicatorConst

variable {p}

@[simp]
theorem coe_indicatorConst {s : Set α} (hs : MeasurableSet s) (hμs : μ s ≠ ∞) (c : E) :
    (↑(indicatorConst p hs hμs c) : lp E p μ) = indicatorConstLp p hs hμs c :=
  rfl
#align measure_theory.Lp.simple_func.coe_indicator_const MeasureTheory.lp.simpleFunc.coe_indicatorConst

theorem toSimpleFunc_indicatorConst {s : Set α} (hs : MeasurableSet s) (hμs : μ s ≠ ∞) (c : E) :
    toSimpleFunc (indicatorConst p hs hμs c) =ᵐ[μ]
      (SimpleFunc.const _ c).piecewise s hs (SimpleFunc.const _ 0) :=
  lp.simpleFunc.toSimpleFunc_toLp _ _
#align measure_theory.Lp.simple_func.to_simple_func_indicator_const MeasureTheory.lp.simpleFunc.toSimpleFunc_indicatorConst

/-- To prove something for an arbitrary `Lp` simple function, with `0 < p < ∞`, it suffices to show
that the property holds for (multiples of) characteristic functions of finite-measure measurable
sets and is closed under addition (of functions with disjoint support). -/
@[elab_as_elim]
protected theorem induction (hp_pos : p ≠ 0) (hp_ne_top : p ≠ ∞) {P : lp.simpleFunc E p μ → Prop}
    (h_ind :
      ∀ (c : E) {s : Set α} (hs : MeasurableSet s) (hμs : μ s < ∞),
        P (lp.simpleFunc.indicatorConst p hs hμs.Ne c))
    (h_add :
      ∀ ⦃f g : α →ₛ E⦄,
        ∀ hf : Memℒp f p μ,
          ∀ hg : Memℒp g p μ,
            Disjoint (support f) (support g) →
              P (lp.simpleFunc.toLp f hf) →
                P (lp.simpleFunc.toLp g hg) → P (lp.simpleFunc.toLp f hf + lp.simpleFunc.toLp g hg))
    (f : lp.simpleFunc E p μ) : P f :=
  by
  suffices ∀ f : α →ₛ E, ∀ hf : mem_ℒp f p μ, P (to_Lp f hf)
    by
    rw [← to_Lp_to_simple_func f]
    apply this
  clear f
  refine' simple_func.induction _ _
  · intro c s hs hf
    by_cases hc : c = 0
    · convert h_ind 0 MeasurableSet.empty (by simp) using 1
      ext1
      simp [hc]
    exact h_ind c hs (simple_func.measure_lt_top_of_mem_ℒp_indicator hp_pos hp_ne_top hc hs hf)
  · intro f g hfg hf hg hfg'
    obtain ⟨hf', hg'⟩ : mem_ℒp f p μ ∧ mem_ℒp g p μ :=
      (mem_ℒp_add_of_disjoint hfg f.strongly_measurable g.strongly_measurable).mp hfg'
    exact h_add hf' hg' hfg (hf hf') (hg hg')
#align measure_theory.Lp.simple_func.induction MeasureTheory.lp.simpleFunc.induction

end Induction

section CoeToLp

variable [Fact (1 ≤ p)]

protected theorem uniformContinuous : UniformContinuous (coe : lp.simpleFunc E p μ → lp E p μ) :=
  uniformContinuous_comap
#align measure_theory.Lp.simple_func.uniform_continuous MeasureTheory.lp.simpleFunc.uniformContinuous

protected theorem uniformEmbedding : UniformEmbedding (coe : lp.simpleFunc E p μ → lp E p μ) :=
  uniformEmbedding_comap Subtype.val_injective
#align measure_theory.Lp.simple_func.uniform_embedding MeasureTheory.lp.simpleFunc.uniformEmbedding

protected theorem uniformInducing : UniformInducing (coe : lp.simpleFunc E p μ → lp E p μ) :=
  simpleFunc.uniformEmbedding.to_uniformInducing
#align measure_theory.Lp.simple_func.uniform_inducing MeasureTheory.lp.simpleFunc.uniformInducing

protected theorem denseEmbedding (hp_ne_top : p ≠ ∞) :
    DenseEmbedding (coe : lp.simpleFunc E p μ → lp E p μ) :=
  by
  borelize E
  apply simple_func.uniform_embedding.dense_embedding
  intro f
  rw [mem_closure_iff_seq_limit]
  have hfi' : mem_ℒp f p μ := Lp.mem_ℒp f
  haveI : separable_space (range f ∪ {0} : Set E) :=
    (Lp.strongly_measurable f).separableSpace_range_union_singleton
  refine'
    ⟨fun n =>
      ↑(to_Lp
          (simple_func.approx_on f (Lp.strongly_measurable f).Measurable (range f ∪ {0}) 0 (by simp)
            n)
          (simple_func.mem_ℒp_approx_on_range (Lp.strongly_measurable f).Measurable hfi' n)),
      fun n => mem_range_self _, _⟩
  convert simple_func.tendsto_approx_on_range_Lp hp_ne_top (Lp.strongly_measurable f).Measurable
      hfi'
  rw [to_Lp_coe_fn f (Lp.mem_ℒp f)]
#align measure_theory.Lp.simple_func.dense_embedding MeasureTheory.lp.simpleFunc.denseEmbedding

protected theorem denseInducing (hp_ne_top : p ≠ ∞) :
    DenseInducing (coe : lp.simpleFunc E p μ → lp E p μ) :=
  (simpleFunc.denseEmbedding hp_ne_top).to_denseInducing
#align measure_theory.Lp.simple_func.dense_inducing MeasureTheory.lp.simpleFunc.denseInducing

protected theorem denseRange (hp_ne_top : p ≠ ∞) :
    DenseRange (coe : lp.simpleFunc E p μ → lp E p μ) :=
  (simpleFunc.denseInducing hp_ne_top).dense
#align measure_theory.Lp.simple_func.dense_range MeasureTheory.lp.simpleFunc.denseRange

variable [NormedField 𝕜] [NormedSpace 𝕜 E]

variable (α E 𝕜)

/-- The embedding of Lp simple functions into Lp functions, as a continuous linear map. -/
def coeToLp : lp.simpleFunc E p μ →L[𝕜] lp E p μ :=
  {
    AddSubgroup.subtype
      (lp.simpleFunc E p μ) with
    map_smul' := fun k f => rfl
    cont := lp.simpleFunc.uniformContinuous.Continuous }
#align measure_theory.Lp.simple_func.coe_to_Lp MeasureTheory.lp.simpleFunc.coeToLp

variable {α E 𝕜}

end CoeToLp

section Order

variable {G : Type _} [NormedLatticeAddCommGroup G]

theorem coeFn_le (f g : lp.simpleFunc G p μ) : f ≤ᵐ[μ] g ↔ f ≤ g := by
  rw [← Subtype.coe_le_coe, ← Lp.coe_fn_le, coeFn_coe_base', coeFn_coe_base' g]
#align measure_theory.Lp.simple_func.coe_fn_le MeasureTheory.lp.simpleFunc.coeFn_le

instance : CovariantClass (lp.simpleFunc G p μ) (lp.simpleFunc G p μ) (· + ·) (· ≤ ·) :=
  by
  refine' ⟨fun f g₁ g₂ hg₁₂ => _⟩
  rw [← Lp.simple_func.coe_fn_le] at hg₁₂⊢
  have h_add_1 : ⇑(f + g₁) =ᵐ[μ] f + g₁ := Lp.coe_fn_add _ _
  have h_add_2 : ⇑(f + g₂) =ᵐ[μ] f + g₂ := Lp.coe_fn_add _ _
  filter_upwards [h_add_1, h_add_2, hg₁₂]with _ h1 h2 h3
  rw [h1, h2, Pi.add_apply, Pi.add_apply]
  exact add_le_add le_rfl h3

variable (p μ G)

theorem coeFn_zero : (0 : lp.simpleFunc G p μ) =ᵐ[μ] (0 : α → G) :=
  lp.coeFn_zero _ _ _
#align measure_theory.Lp.simple_func.coe_fn_zero MeasureTheory.lp.simpleFunc.coeFn_zero

variable {p μ G}

theorem coeFn_nonneg (f : lp.simpleFunc G p μ) : 0 ≤ᵐ[μ] f ↔ 0 ≤ f :=
  by
  rw [← Lp.simple_func.coe_fn_le]
  have h0 : (0 : Lp.simple_func G p μ) =ᵐ[μ] (0 : α → G) := Lp.simple_func.coe_fn_zero p μ G
  constructor <;> intro h <;> filter_upwards [h, h0]with _ _ h2
  · rwa [h2]
  · rwa [← h2]
#align measure_theory.Lp.simple_func.coe_fn_nonneg MeasureTheory.lp.simpleFunc.coeFn_nonneg

theorem exists_simpleFunc_nonneg_ae_eq {f : lp.simpleFunc G p μ} (hf : 0 ≤ f) :
    ∃ f' : α →ₛ G, 0 ≤ f' ∧ f =ᵐ[μ] f' :=
  by
  rw [← Lp.simple_func.coe_fn_nonneg] at hf
  have hf_ae : 0 ≤ᵐ[μ] simple_func.to_simple_func f :=
    by
    filter_upwards [to_simple_func_eq_to_fun f, hf]with _ h1 _
    rwa [h1]
  let s := to_measurable μ { x | ¬0 ≤ simple_func.to_simple_func f x }ᶜ
  have hs_zero : μ (sᶜ) = 0 := by
    rw [compl_compl, measure_to_measurable]
    rwa [eventually_le, ae_iff] at hf_ae
  have hfs_nonneg : ∀ x ∈ s, 0 ≤ simple_func.to_simple_func f x :=
    by
    intro x hxs
    rw [mem_compl_iff] at hxs
    have hx' : x ∉ { a : α | ¬0 ≤ simple_func.to_simple_func f a } := fun h =>
      hxs (subset_to_measurable μ _ h)
    rwa [Set.nmem_setOf_iff, Classical.not_not] at hx'
  let f' :=
    simple_func.piecewise s (measurable_set_to_measurable μ _).compl (simple_func.to_simple_func f)
      (simple_func.const α (0 : G))
  refine' ⟨f', fun x => _, _⟩
  · rw [simple_func.piecewise_apply]
    by_cases hxs : x ∈ s
    · simp only [hxs, hfs_nonneg x hxs, if_true, Pi.zero_apply, simple_func.coe_zero]
    · simp only [hxs, simple_func.const_zero, if_false]
  · rw [simple_func.coe_piecewise]
    have : s =ᵐ[μ] univ := by
      rw [ae_eq_set]
      simp only [true_and_iff, measure_empty, eq_self_iff_true, diff_univ, ← compl_eq_univ_diff]
      exact hs_zero
    refine' eventually_eq.trans (to_simple_func_eq_to_fun f).symm _
    refine' eventually_eq.trans _ (piecewise_ae_eq_of_ae_eq_set this.symm)
    simp only [simple_func.const_zero, indicator_univ, piecewise_eq_indicator, simple_func.coe_zero]
#align measure_theory.Lp.simple_func.exists_simple_func_nonneg_ae_eq MeasureTheory.lp.simpleFunc.exists_simpleFunc_nonneg_ae_eq

variable (p μ G)

/-- Coercion from nonnegative simple functions of Lp to nonnegative functions of Lp. -/
def coeSimpleFuncNonnegToLpNonneg :
    { g : lp.simpleFunc G p μ // 0 ≤ g } → { g : lp G p μ // 0 ≤ g } := fun g => ⟨g, g.2⟩
#align measure_theory.Lp.simple_func.coe_simple_func_nonneg_to_Lp_nonneg MeasureTheory.lp.simpleFunc.coeSimpleFuncNonnegToLpNonneg

theorem denseRange_coeSimpleFuncNonnegToLpNonneg [hp : Fact (1 ≤ p)] (hp_ne_top : p ≠ ∞) :
    DenseRange (coeSimpleFuncNonnegToLpNonneg p μ G) :=
  by
  borelize G
  intro g
  rw [mem_closure_iff_seq_limit]
  have hg_mem_ℒp : mem_ℒp g p μ := Lp.mem_ℒp g
  have zero_mem : (0 : G) ∈ (range g ∪ {0} : Set G) ∩ { y | 0 ≤ y } := by
    simp only [union_singleton, mem_inter_iff, mem_insert_iff, eq_self_iff_true, true_or_iff,
      mem_set_of_eq, le_refl, and_self_iff]
  have : separable_space ((range g ∪ {0}) ∩ { y | 0 ≤ y } : Set G) :=
    by
    apply is_separable.separable_space
    apply is_separable.mono _ (Set.inter_subset_left _ _)
    exact
      (Lp.strongly_measurable (g : Lp G p μ)).isSeparable_range.union
        (finite_singleton _).IsSeparable
  have g_meas : Measurable g := (Lp.strongly_measurable (g : Lp G p μ)).Measurable
  let x n := simple_func.approx_on g g_meas ((range g ∪ {0}) ∩ { y | 0 ≤ y }) 0 zero_mem n
  have hx_nonneg : ∀ n, 0 ≤ x n := by
    intro n a
    change x n a ∈ { y : G | 0 ≤ y }
    have A : (range g ∪ {0} : Set G) ∩ { y | 0 ≤ y } ⊆ { y | 0 ≤ y } := inter_subset_right _ _
    apply A
    exact simple_func.approx_on_mem g_meas _ n a
  have hx_mem_ℒp : ∀ n, mem_ℒp (x n) p μ :=
    simple_func.mem_ℒp_approx_on _ hg_mem_ℒp _ ⟨ae_strongly_measurable_const, by simp⟩
  have h_to_Lp := fun n => mem_ℒp.coe_fn_to_Lp (hx_mem_ℒp n)
  have hx_nonneg_Lp : ∀ n, 0 ≤ to_Lp (x n) (hx_mem_ℒp n) :=
    by
    intro n
    rw [← Lp.simple_func.coe_fn_le, coeFn_coe_base' (simple_func.to_Lp (x n) _),
      Lp.simple_func.to_Lp_eq_to_Lp]
    have h0 := Lp.simple_func.coe_fn_zero p μ G
    filter_upwards [Lp.simple_func.coe_fn_zero p μ G, h_to_Lp n]with a ha0 ha_to_Lp
    rw [ha0, ha_to_Lp]
    exact hx_nonneg n a
  have hx_tendsto : tendsto (fun n : ℕ => snorm (x n - g) p μ) at_top (𝓝 0) :=
    by
    apply simple_func.tendsto_approx_on_Lp_snorm g_meas zero_mem hp_ne_top
    · have hg_nonneg : 0 ≤ᵐ[μ] g := (Lp.coe_fn_nonneg _).mpr g.2
      refine' hg_nonneg.mono fun a ha => subset_closure _
      simpa using ha
    · simp_rw [sub_zero]
      exact hg_mem_ℒp.snorm_lt_top
  refine'
    ⟨fun n =>
      (coe_simple_func_nonneg_to_Lp_nonneg p μ G) ⟨to_Lp (x n) (hx_mem_ℒp n), hx_nonneg_Lp n⟩,
      fun n => mem_range_self _, _⟩
  suffices tendsto (fun n : ℕ => ↑(to_Lp (x n) (hx_mem_ℒp n))) at_top (𝓝 (g : Lp G p μ))
    by
    rw [tendsto_iff_dist_tendsto_zero] at this⊢
    simp_rw [Subtype.dist_eq]
    convert this
  rw [Lp.tendsto_Lp_iff_tendsto_ℒp']
  convert hx_tendsto
  refine' funext fun n => snorm_congr_ae (eventually_eq.sub _ _)
  · rw [Lp.simple_func.to_Lp_eq_to_Lp]
    exact h_to_Lp n
  · rw [← coeFn_coeBase]
#align measure_theory.Lp.simple_func.dense_range_coe_simple_func_nonneg_to_Lp_nonneg MeasureTheory.lp.simpleFunc.denseRange_coeSimpleFuncNonnegToLpNonneg

variable {p μ G}

end Order

end SimpleFunc

end Lp

variable [MeasurableSpace α] [NormedAddCommGroup E] {f : α → E} {p : ℝ≥0∞} {μ : Measure α}

/-- To prove something for an arbitrary `Lp` function in a second countable Borel normed group, it
suffices to show that
* the property holds for (multiples of) characteristic functions;
* is closed under addition;
* the set of functions in `Lp` for which the property holds is closed.
-/
@[elab_as_elim]
theorem lp.induction [_i : Fact (1 ≤ p)] (hp_ne_top : p ≠ ∞) (P : lp E p μ → Prop)
    (h_ind :
      ∀ (c : E) {s : Set α} (hs : MeasurableSet s) (hμs : μ s < ∞),
        P (lp.simpleFunc.indicatorConst p hs hμs.Ne c))
    (h_add :
      ∀ ⦃f g⦄,
        ∀ hf : Memℒp f p μ,
          ∀ hg : Memℒp g p μ,
            Disjoint (support f) (support g) →
              P (hf.toLp f) → P (hg.toLp g) → P (hf.toLp f + hg.toLp g))
    (h_closed : IsClosed { f : lp E p μ | P f }) : ∀ f : lp E p μ, P f :=
  by
  refine' fun f => (Lp.simple_func.dense_range hp_ne_top).inductionOn f h_closed _
  refine' Lp.simple_func.induction (lt_of_lt_of_le zero_lt_one _i.elim).ne' hp_ne_top _ _
  · exact fun c s => h_ind c
  · exact fun f g hf hg => h_add hf hg
#align measure_theory.Lp.induction MeasureTheory.lp.induction

/-- To prove something for an arbitrary `mem_ℒp` function in a second countable
Borel normed group, it suffices to show that
* the property holds for (multiples of) characteristic functions;
* is closed under addition;
* the set of functions in the `Lᵖ` space for which the property holds is closed.
* the property is closed under the almost-everywhere equal relation.

It is possible to make the hypotheses in the induction steps a bit stronger, and such conditions
can be added once we need them (for example in `h_add` it is only necessary to consider the sum of
a simple function with a multiple of a characteristic function and that the intersection
of their images is a subset of `{0}`).
-/
@[elab_as_elim]
theorem Memℒp.induction [_i : Fact (1 ≤ p)] (hp_ne_top : p ≠ ∞) (P : (α → E) → Prop)
    (h_ind : ∀ (c : E) ⦃s⦄, MeasurableSet s → μ s < ∞ → P (s.indicator fun _ => c))
    (h_add :
      ∀ ⦃f g : α → E⦄,
        Disjoint (support f) (support g) → Memℒp f p μ → Memℒp g p μ → P f → P g → P (f + g))
    (h_closed : IsClosed { f : lp E p μ | P f })
    (h_ae : ∀ ⦃f g⦄, f =ᵐ[μ] g → Memℒp f p μ → P f → P g) : ∀ ⦃f : α → E⦄ (hf : Memℒp f p μ), P f :=
  by
  have : ∀ f : simple_func α E, mem_ℒp f p μ → P f :=
    by
    refine' simple_func.induction _ _
    · intro c s hs h
      by_cases hc : c = 0
      · subst hc
        convert h_ind 0 MeasurableSet.empty (by simp) using 1
        ext
        simp [const]
      have hp_pos : p ≠ 0 := (lt_of_lt_of_le zero_lt_one _i.elim).ne'
      exact h_ind c hs (simple_func.measure_lt_top_of_mem_ℒp_indicator hp_pos hp_ne_top hc hs h)
    · intro f g hfg hf hg int_fg
      rw [simple_func.coe_add,
        mem_ℒp_add_of_disjoint hfg f.strongly_measurable g.strongly_measurable] at int_fg
      refine' h_add hfg int_fg.1 int_fg.2 (hf int_fg.1) (hg int_fg.2)
  have : ∀ f : Lp.simple_func E p μ, P f := by
    intro f
    exact
      h_ae (Lp.simple_func.to_simple_func_eq_to_fun f) (Lp.simple_func.mem_ℒp f)
        (this (Lp.simple_func.to_simple_func f) (Lp.simple_func.mem_ℒp f))
  have : ∀ f : Lp E p μ, P f := fun f =>
    (Lp.simple_func.dense_range hp_ne_top).inductionOn f h_closed this
  exact fun f hf => h_ae hf.coeFn_toLp (Lp.mem_ℒp _) (this (hf.toLp f))
#align measure_theory.mem_ℒp.induction MeasureTheory.Memℒp.induction

section Integrable

-- mathport name: «expr →₁ₛ[ ] »
notation:25 α " →₁ₛ[" μ "] " E => @MeasureTheory.lp.simpleFunc α E _ _ 1 μ

theorem L1.SimpleFunc.toLp_one_eq_toL1 (f : α →ₛ E) (hf : Integrable f μ) :
    (lp.simpleFunc.toLp f (memℒp_one_iff_integrable.2 hf) : α →₁[μ] E) = hf.toL1 f :=
  rfl
#align measure_theory.L1.simple_func.to_Lp_one_eq_to_L1 MeasureTheory.L1.SimpleFunc.toLp_one_eq_toL1

protected theorem L1.SimpleFunc.integrable (f : α →₁ₛ[μ] E) :
    Integrable (lp.simpleFunc.toSimpleFunc f) μ :=
  by
  rw [← mem_ℒp_one_iff_integrable]
  exact Lp.simple_func.mem_ℒp f
#align measure_theory.L1.simple_func.integrable MeasureTheory.L1.SimpleFunc.integrable

/-- To prove something for an arbitrary integrable function in a normed group,
it suffices to show that
* the property holds for (multiples of) characteristic functions;
* is closed under addition;
* the set of functions in the `L¹` space for which the property holds is closed.
* the property is closed under the almost-everywhere equal relation.

It is possible to make the hypotheses in the induction steps a bit stronger, and such conditions
can be added once we need them (for example in `h_add` it is only necessary to consider the sum of
a simple function with a multiple of a characteristic function and that the intersection
of their images is a subset of `{0}`).
-/
@[elab_as_elim]
theorem Integrable.induction (P : (α → E) → Prop)
    (h_ind : ∀ (c : E) ⦃s⦄, MeasurableSet s → μ s < ∞ → P (s.indicator fun _ => c))
    (h_add :
      ∀ ⦃f g : α → E⦄,
        Disjoint (support f) (support g) → Integrable f μ → Integrable g μ → P f → P g → P (f + g))
    (h_closed : IsClosed { f : α →₁[μ] E | P f })
    (h_ae : ∀ ⦃f g⦄, f =ᵐ[μ] g → Integrable f μ → P f → P g) :
    ∀ ⦃f : α → E⦄ (hf : Integrable f μ), P f :=
  by
  simp only [← mem_ℒp_one_iff_integrable] at *
  exact mem_ℒp.induction one_ne_top P h_ind h_add h_closed h_ae
#align measure_theory.integrable.induction MeasureTheory.Integrable.induction

end Integrable

end MeasureTheory


def foo (x : Nat) :=
  2*x

def boo (x : Nat) :=
  x + x

@[csimp] theorem foo_eq_boo1 (x : Nat) : foo x = boo x := by -- Error
  simp [foo, boo, Nat.mul_comm]
  show (x * 1) + x = x + x
  simp

@[csimp] theorem foo_eq_boo2 : foo = boo :=
  funext foo_eq_boo1

set_option trace.compiler.ir.init true
def f (x : Nat) : Nat :=
  foo (foo x)


-- Eliminacion_de_la_equivalencia.lean
-- Eliminación de la equivalencia.
-- José A. Alonso Jiménez
-- Sevilla, 12 de agosto de 2020
-- ---------------------------------------------------------------------

-- En este relación se muestra distintas formas de demostrar un teorema
-- con eliminación de la equivalencia

-- ---------------------------------------------------------------------
-- Ejercicio. Realizar las siguientes acciones:
-- 1. Importar la librería de tácticas.
-- 2. Declarar P, Q y R como variables sobre proposiciones.
-- ----------------------------------------------------------------------

import tactic              -- 1
variables (P Q R : Prop)   -- 2

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si
--    P ↔ Q
--    Q → R
-- entonces
--    P → R
-- ----------------------------------------------------------------------

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

example
  (h : P ↔ Q)
  (hQR : Q → R)
  : P → R :=
begin
  intro hP,
  apply hQR,
  cases h with hPQ hQP,
  apply hPQ,
  exact hP,
end

-- Prueba:
/-
  P Q R : Prop,
  h : P ↔ Q,
  hQR : Q → R
  ⊢ P → R
intro hP,
  hP : P
  ⊢ R
apply hQR,
  ⊢ Q
cases h with hPQ hQP,
  hPQ : P → Q,
  hQP : Q → P
  ⊢ Q
apply hPQ,
  ⊢ P
exact hP,
  no goals
-/

-- Comentarios:
-- + La táctica (cases h with h1 h2), cuando la hipótesis h es una
--   equivalencia aplica la regla de eliminación de la equivalencia; es
--   decir, si h es (P ↔ Q), entonces elimina h y añade las hipótesis
--   (h1 : P → Q) y (h2 : Q → P).

-- 2ª demostración (simplificando los últimos pasos de la anterior)
-- ================================================================

example
  (h : P ↔ Q)
  (hQR : Q → R)
  : P → R :=
begin
  intro hP,
  apply hQR,
  cases h with hPQ hQP,
  exact hPQ hP,
end

-- Prueba:
/-
  P Q R : Prop,
  h : P ↔ Q,
  hQR : Q → R
  ⊢ P → R
intro hP,
  hP : P
  ⊢ R
apply hQR,
  ⊢ Q
cases h with hPQ hQP,
  hPQ : P → Q,
  hQP : Q → P
  ⊢ Q
exact hPQ hP,
  no goals
-/

-- 3ª demostración (simplificando los últimos pasos de la anterior)
-- ================================================================

example
  (h : P ↔ Q)
  (hQR : Q → R)
  : P → R :=
begin
  intro hP,
  exact hQR (h.1 hP),
end

-- Comentarios:
-- + Si h es la equivalencia (P ↔ Q), entonces h.1 es (P → Q) y h.2 es
--   (Q → P).

-- 4ª demostración (por un término)
-- ================================

example
  (h : P ↔ Q)
  (hQR : Q → R)
  : P → R :=
λ hP, hQR (h.1 hP)

-- 5ª demostración (por reescritura)
-- =================================

example
  (h : P ↔ Q)
  (hQR : Q → R)
  : P → R :=
begin
  rw h,
  exact hQR,
end

-- Prueba:
/-
  P Q R : Prop,
  h : P ↔ Q,
  hQR : Q → R
  ⊢ P → R
rw h,
  ⊢ Q → R
exact hQR,
  no goals
-/

-- Comentarios:
-- + La táctica (rw h), cuando h es una equivalencia como (P ↔ Q),
--   sustituye en la conclusión P por Q.

-- 6ª demostración (por reescritura en hipótesis)
-- ==============================================

example
  (h : P ↔ Q)
  (hQR : Q → R)
  : P → R :=
begin
  rw ← h at hQR,
  exact hQR,
end

-- Prueba:
/-
  P Q R : Prop,
  h : P ↔ Q,
  hQR : Q → R
  ⊢ P → R
rw ← h at hQR,
  hQR : P → R
  ⊢ P → R
exact hQR,
  no goals
-/

-- Comentarios:
-- + La táctica (rw ← h at h'), cuando h es una equivalencia como (P ↔- Q),
--   sustituye en la  hipótesis h' la fórmula Q por P.

-- 7ª demostración (estructurada)
-- ==============================

example
  (h : P ↔ Q)
  (hQR : Q → R)
  : P → R :=
begin
  assume hP : P,
  have hQ : Q, from h.1 hP,
  show R, from hQR hQ,
end

-- Comentarios:
-- + La táctica (assume h : P), cuando la conclusión es de la forma
--   (P → Q), añade la hipótesis P y cambia la conclusión a Q.
-- + La táctica (have h : e) genera dos subojetivos: el primero tiene
--   como conclusión e y el segundo tiene la conclusión actual pero se le
--   añade la hipótesis (h : e).
-- + la táctica (show P, from h) demuestra la conclusión con la prueba h.

-- 8ª demostración (estructurada)
-- ==============================

example
  (h : P ↔ Q)
  (hQR : Q → R)
  : P → R :=
assume hP, hQR (h.1 hP)

-- 9ª demostración (automática)
-- ============================

example
  (h : P ↔ Q)
  (hQR : Q → R)
  : P → R :=
by tauto


import Algorithm.Data.Classes.AssocArray
import Mathlib.Data.List.Nodup

structure Graph where
  V : Type _
  [decidableEqV : DecidableEq V]
  EType : Type _
  fst' : EType → V
  snd' : EType → V
  StarList : Type _
  [assocArrayStarList : AssocArray V (List EType) StarList]
  star' : StarList
  costar' : StarList
  fst_star' : ∀ v, ∀ e ∈ AssocArray.get star' v, fst' e = v
  snd_costar' : ∀ v, ∀ e ∈ AssocArray.get costar' v, snd' e = v
  nodup_star' : ∀ v, (AssocArray.get star' v).Nodup
  nodup_costar' : ∀ v, (AssocArray.get costar' v).Nodup
  mem_star'_iff_mem_costar' : ∀ e,
    e ∈ AssocArray.get star' (fst' e) ↔ e ∈ AssocArray.get costar' (snd' e)

namespace Graph

attribute [instance] decidableEqV assocArrayStarList

variable {g : Graph}
-- instance : GetElem g.StarList g.V (List g.E) (fun _ _ ↦ True) := AssocArray.instGetElem
-- by infer_instance

def EType.fst (e : g.EType) : g.V := g.fst' e
def EType.snd (e : g.EType) : g.V := g.snd' e

def V.star (v : g.V) : List g.EType := AssocArray.get g.star' v
def V.costar (v : g.V) : List g.EType := AssocArray.get g.costar' v

lemma fst_star (v : g.V) : ∀ e ∈ v.star, e.fst = v := g.fst_star' v
lemma snd_costar (v : g.V) : ∀ e ∈ v.costar, e.snd = v := g.snd_costar' v

lemma nodup_star (v : g.V) : v.star.Nodup := g.nodup_star' v
lemma nodup_costar (v : g.V) : v.costar.Nodup := g.nodup_costar' v

lemma mem_star_iff_mem_costar (e : g.EType) : e ∈ e.fst.star ↔ e ∈ e.snd.costar :=
  g.mem_star'_iff_mem_costar' e

def E (g : Graph) := {e : g.EType // e ∈ e.fst.star}

def E.fst (e : g.E) : g.V := e.val.fst
def E.snd (e : g.E) : g.V := e.val.snd

lemma E.mem_star (e : g.E) : e.val ∈ e.fst.star := e.2
lemma E.mem_costar (e : g.E) : e.val ∈ e.snd.costar := (mem_star_iff_mem_costar _).mp e.2

end Graph