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

import tactic
       data.polynomial
       data.real.basic

-- set_option profiler true
/-!

# Polynomial derivations

We prove the structure theorem for polynomial derivations.

# Main definitions

* `polynomial_derivation (R : Type) [comm_ring R]` : the type of `R`-derivations on `R[X]`.

# Main statements

We prove the structure theorem, that every polynomial derivation is equal to
an R[X]-linear multiple of polynomial differentiation.

Proof that there
is an equivalence between polynomials and polynomial derivations, sending
a polynomial h to the derivation mapping f to h*derivative(f).
proof that differentiation is a derivation.

# Further work

-/

-- start of file

/-- A polynomial derivation on a ring R is a function d : R[X] → R[X] satisfying
three axioms:

map_add   : ∀ {f g : polynomial R}      , d (f + g)   = d f + d g
map_C_mul : ∀ (k : R) (f : polynomial R), d (C k * f) = C k * d f
map_mul   : ∀ (f g : polynomial R)      , d (f * g)   = f * d g + g * d f
-/
structure polynomial_derivation (R : Type) [comm_ring R] :=
(to_fun : polynomial R → polynomial R) -- Delta
(map_add' : ∀ (f g : polynomial R), to_fun (f + g) = to_fun f + to_fun g)
(map_C_mul' : ∀ (k : R) (f : polynomial R), to_fun (polynomial.C k * f) = polynomial.C k * to_fun f)
(map_mul' : ∀ (f g : polynomial R), to_fun (f * g) = f * to_fun g + g * to_fun f)

open polynomial

namespace polynomial_derivation

variables {R : Type} [comm_ring R]

/-- think of a polynomial derivation as a function from R[X] to R[X] -/
instance : has_coe_to_fun (polynomial_derivation R) :=
{ F := λ _, polynomial R → polynomial R,
  coe := to_fun
}

@[simp]
theorem map_add (d : polynomial_derivation R) : ∀ {f g : polynomial R}, d (f + g) = d f + d g := d.map_add'

@[simp]
theorem map_mul (d : polynomial_derivation R): ∀ (f g : polynomial R), d (f * g) = f * d g + g * d f := d.map_mul'

@[simp]
theorem map_C_mul (d : polynomial_derivation R): ∀ (k : R) (f : polynomial R), d (C k * f) = C k * d f := d.map_C_mul'

@[simp]
lemma map_one (d : polynomial_derivation R) : d 1 = 0 :=
begin
  have hd : d 1 + d 1 = d 1 := begin
    conv begin
      to_rhs, rw (show (1 : polynomial R) = 1 * 1, by simp),
    end,
    rw map_mul, ring,
  end,
  rwa add_left_eq_self at hd,
end

@[simp]
lemma map_C (d : polynomial_derivation R) (a : R) : d (C a) = 0 :=
by rw [(show C a = C a * (1 : polynomial R), from (mul_one _).symm),
      map_C_mul, map_one, mul_zero]

lemma map_pow_succ_aux (n : ℕ)
  (d : polynomial_derivation R) :
  X * ((↑n + 1) * X ^ n * d X) + X * X ^ n * d X =
    (↑n + 1 + 1) * (X * X ^ n) * d X := by ring

-- Leibniz rule
lemma map_pow_succ (d : polynomial_derivation R) (n : ℕ) : d (X ^ (n + 1)) = (n + 1)*X^n * d(X) :=
begin
  induction n with n IH,
  {simp},
  rw pow_succ, rw map_mul,
  rw IH,
  simp only [pow_succ, nat.succ_eq_add_one],
  push_cast,
  exact map_pow_succ_aux n d,
end

lemma structure_theorem_aux' (a : R) (n : ℕ) (d : polynomial_derivation R) : C a * X ^ n * d X + X * (d X * derivative (C a * X ^ n)) =
    d X * (derivative (C a * X ^ n) * X + C a * X ^ n * 1) := by ring

/-- structure theorem for polynomial derivations -/
theorem structure_theorem (d : polynomial_derivation R) (f : polynomial R): d f = d X * polynomial.derivative f :=
begin
  apply f.induction_on,
  {intro a, simp [map_C]},
  { intros _ _ hp hq,
    rw [map_add, hp, hq, derivative_add, mul_add],},
  intros n a h,
  rw [pow_succ, mul_comm X, ←mul_assoc, map_mul, derivative_mul, h, derivative_X],
  exact structure_theorem_aux' a n d,
end

@[ext] theorem ext : ∀ {d₁ d₂ : polynomial_derivation R} (H : ∀ f, d₁ f = d₂ f), d₁ = d₂
| ⟨_, _, _, _⟩ ⟨_, _, _, _⟩ H := by congr; exact funext H

lemma structure_classification_aux1
  (j : polynomial R)
  (k : R)
  (f : polynomial R) :
  j * (0 * f + C k * derivative f) = C k * (j * derivative f) := by ring

lemma structure_classification_aux2
  (j f g : polynomial R) :
  j * (derivative f * g + f * derivative g) =
    f * (j * derivative g) + g * (j * derivative f) := by ring

noncomputable definition structure_classification (R : Type) [comm_ring R] :
  polynomial_derivation R ≃ polynomial R :=
{ to_fun := λ d, d X,
  inv_fun := λ j,
    { to_fun := λ f, j * f.derivative,
      map_add' := λ _ _, by rw [derivative_add, mul_add],
      map_C_mul' := begin
        intros,
        rw [derivative_mul, derivative_C],
        exact structure_classification_aux1 j k f,
      end,
      map_mul' := begin
        intros,
        rw derivative_mul,
        exact structure_classification_aux2 j f g,
      end },
  left_inv := begin
    intro d,
    ext1 f,
    rw structure_theorem d f,
    refl,
  end,
  right_inv := begin
    intro p,
    change p * derivative X = p,
    simp only [mul_one, polynomial.derivative_X],
  end }

end polynomial_derivation


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

! This file was ported from Lean 3 source module algebra.category.fgModule.basic
! leanprover-community/mathlib commit 19a70dceb9dff0994b92d2dd049de7d84d28112b
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.CategoryTheory.Monoidal.Rigid.Basic
import Mathbin.CategoryTheory.Monoidal.Subcategory
import Mathbin.LinearAlgebra.Coevaluation
import Mathbin.Algebra.Category.Module.Monoidal

/-!
# The category of finitely generated modules over a ring

This introduces `fgModule R`, the category of finitely generated modules over a ring `R`.
It is implemented as a full subcategory on a subtype of `Module R`.

When `K` is a field, `fgModule K` is the category of finite dimensional vector spaces over `K`.

We first create the instance as a preadditive category.
When `R` is commutative we then give the structure as an `R`-linear monoidal category.
When `R` is a field we give it the structure of a closed monoidal category
and then as a right-rigid monoidal category.

## Future work

* Show that `fgModule R` is abelian when `R` is (left)-noetherian.

-/

noncomputable section

open CategoryTheory ModuleCat.monoidalCategory

open Classical BigOperators

universe u

section Ring

variable (R : Type u) [Ring R]

/-- Define `fgModule` as the subtype of `Module.{u} R` of finitely generated modules. -/
def FgModule :=
  FullSubcategory fun V : ModuleCat.{u} R => Module.Finite R V deriving LargeCategory,
  ConcreteCategory, Preadditive
#align fgModule FgModule

end Ring

namespace FgModule

section Ring

variable (R : Type u) [Ring R]

instance finite (V : FgModule R) : Module.Finite R V.obj :=
  V.property
#align fgModule.finite FgModule.finite

instance : Inhabited (FgModule R) :=
  ⟨⟨ModuleCat.of R R, Module.Finite.self R⟩⟩

/-- Lift an unbundled finitely generated module to `fgModule R`. -/
def of (V : Type u) [AddCommGroup V] [Module R V] [Module.Finite R V] : FgModule R :=
  ⟨ModuleCat.of R V, by
    change Module.Finite R V
    infer_instance⟩
#align fgModule.of FgModule.of

instance (V : FgModule R) : Module.Finite R V.obj :=
  V.property

instance : HasForget₂ (FgModule.{u} R) (ModuleCat.{u} R) :=
  by
  dsimp [FgModule]
  infer_instance

instance : Full (forget₂ (FgModule R) (ModuleCat.{u} R)) where preimage X Y f := f

variable {R}

/-- Converts and isomorphism in the category `fgModule R` to a `linear_equiv` between the underlying
modules. -/
def isoToLinearEquiv {V W : FgModule R} (i : V ≅ W) : V.obj ≃ₗ[R] W.obj :=
  ((forget₂ (FgModule.{u} R) (ModuleCat.{u} R)).mapIso i).toLinearEquiv
#align fgModule.iso_to_linear_equiv FgModule.isoToLinearEquiv

/-- Converts a `linear_equiv` to an isomorphism in the category `fgModule R`. -/
@[simps]
def LinearEquiv.toFgModuleIso {V W : Type u} [AddCommGroup V] [Module R V] [Module.Finite R V]
    [AddCommGroup W] [Module R W] [Module.Finite R W] (e : V ≃ₗ[R] W) :
    FgModule.of R V ≅ FgModule.of R W
    where
  Hom := e.toLinearMap
  inv := e.symm.toLinearMap
  hom_inv_id' := by
    ext
    exact e.left_inv x
  inv_hom_id' := by
    ext
    exact e.right_inv x
#align linear_equiv.to_fgModule_iso LinearEquiv.toFgModuleIso

end Ring

section CommRing

variable (R : Type u) [CommRing R]

instance : Linear R (FgModule R) := by dsimp_result => dsimp [FgModule]; infer_instance

instance monoidalPredicate_module_finite :
    MonoidalCategory.MonoidalPredicate fun V : ModuleCat.{u} R => Module.Finite R V
    where
  prop_id' := Module.Finite.self R
  prop_tensor' X Y hX hY := Module.Finite.tensorProduct R X Y
#align fgModule.monoidal_predicate_module_finite FgModule.monoidalPredicate_module_finite

instance : MonoidalCategory (FgModule R) := by dsimp_result => dsimp [FgModule]; infer_instance

instance : SymmetricCategory (FgModule R) := by dsimp_result => dsimp [FgModule]; infer_instance

instance : MonoidalPreadditive (FgModule R) := by dsimp_result => dsimp [FgModule]; infer_instance

instance : MonoidalLinear R (FgModule R) := by dsimp_result => dsimp [FgModule]; infer_instance

/-- The forgetful functor `fgModule R ⥤ Module R` as a monoidal functor. -/
def forget₂Monoidal : MonoidalFunctor (FgModule R) (ModuleCat.{u} R) :=
  MonoidalCategory.fullMonoidalSubcategoryInclusion _
#align fgModule.forget₂_monoidal FgModule.forget₂Monoidal

instance forget₂Monoidal_faithful : Faithful (forget₂Monoidal R).toFunctor :=
  by
  dsimp [forget₂_monoidal]
  infer_instance
#align fgModule.forget₂_monoidal_faithful FgModule.forget₂Monoidal_faithful

instance forget₂Monoidal_additive : (forget₂Monoidal R).toFunctor.Additive :=
  by
  dsimp [forget₂_monoidal]
  infer_instance
#align fgModule.forget₂_monoidal_additive FgModule.forget₂Monoidal_additive

instance forget₂Monoidal_linear : (forget₂Monoidal R).toFunctor.Linear R :=
  by
  dsimp [forget₂_monoidal]
  infer_instance
#align fgModule.forget₂_monoidal_linear FgModule.forget₂Monoidal_linear

theorem Iso.conj_eq_conj {V W : FgModule R} (i : V ≅ W) (f : End V) :
    Iso.conj i f = LinearEquiv.conj (isoToLinearEquiv i) f :=
  rfl
#align fgModule.iso.conj_eq_conj FgModule.Iso.conj_eq_conj

end CommRing

section Field

variable (K : Type u) [Field K]

instance (V W : FgModule K) : Module.Finite K (V ⟶ W) :=
  (by infer_instance : Module.Finite K (V.obj →ₗ[K] W.obj))

instance closedPredicate_module_finite :
    MonoidalCategory.ClosedPredicate fun V : ModuleCat.{u} K => Module.Finite K V
    where prop_ihom' X Y hX hY := @LinearMap.finiteDimensional K _ X _ _ hX Y _ _ hY
#align fgModule.closed_predicate_module_finite FgModule.closedPredicate_module_finite

instance : MonoidalClosed (FgModule K) := by dsimp_result => dsimp [FgModule]; infer_instance

variable (V W : FgModule K)

@[simp]
theorem ihom_obj : (ihom V).obj W = FgModule.of K (V.obj →ₗ[K] W.obj) :=
  rfl
#align fgModule.ihom_obj FgModule.ihom_obj

/-- The dual module is the dual in the rigid monoidal category `fgModule K`. -/
def fgModuleDual : FgModule K :=
  ⟨ModuleCat.of K (Module.Dual K V.obj), Subspace.Module.Dual.finiteDimensional⟩
#align fgModule.fgModule_dual FgModule.fgModuleDual

open CategoryTheory.MonoidalCategory

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- The coevaluation map is defined in `linear_algebra.coevaluation`. -/
def fgModuleCoevaluation : 𝟙_ (FgModule K) ⟶ V ⊗ fgModuleDual K V := by apply coevaluation K V.obj
#align fgModule.fgModule_coevaluation FgModule.fgModuleCoevaluation

theorem fgModuleCoevaluation_apply_one :
    fgModuleCoevaluation K V (1 : K) =
      ∑ i : Basis.ofVectorSpaceIndex K V.obj,
        (Basis.ofVectorSpace K V.obj) i ⊗ₜ[K] (Basis.ofVectorSpace K V.obj).Coord i :=
  by apply coevaluation_apply_one K V.obj
#align fgModule.fgModule_coevaluation_apply_one FgModule.fgModuleCoevaluation_apply_one

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- The evaluation morphism is given by the contraction map. -/
def fgModuleEvaluation : fgModuleDual K V ⊗ V ⟶ 𝟙_ (FgModule K) := by apply contractLeft K V.obj
#align fgModule.fgModule_evaluation FgModule.fgModuleEvaluation

@[simp]
theorem fgModuleEvaluation_apply (f : (fgModuleDual K V).obj) (x : V.obj) :
    (fgModuleEvaluation K V) (f ⊗ₜ x) = f.toFun x := by apply contractLeft_apply f x
#align fgModule.fgModule_evaluation_apply FgModule.fgModuleEvaluation_apply

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
private theorem coevaluation_evaluation :
    let V' : FgModule K := fgModuleDual K V
    (𝟙 V' ⊗ fgModuleCoevaluation K V) ≫ (α_ V' V V').inv ≫ (fgModuleEvaluation K V ⊗ 𝟙 V') =
      (ρ_ V').Hom ≫ (λ_ V').inv :=
  by apply contractLeft_assoc_coevaluation K V.obj
#align fgModule.coevaluation_evaluation fgModule.coevaluation_evaluation

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
private theorem evaluation_coevaluation :
    (fgModuleCoevaluation K V ⊗ 𝟙 V) ≫
        (α_ V (fgModuleDual K V) V).Hom ≫ (𝟙 V ⊗ fgModuleEvaluation K V) =
      (λ_ V).Hom ≫ (ρ_ V).inv :=
  by apply contractLeft_assoc_coevaluation' K V.obj
#align fgModule.evaluation_coevaluation fgModule.evaluation_coevaluation

instance exactPairing : ExactPairing V (fgModuleDual K V)
    where
  coevaluation := fgModuleCoevaluation K V
  evaluation := fgModuleEvaluation K V
  coevaluation_evaluation' := coevaluation_evaluation K V
  evaluation_coevaluation' := evaluation_coevaluation K V
#align fgModule.exact_pairing FgModule.exactPairing

instance rightDual : HasRightDual V :=
  ⟨fgModuleDual K V⟩
#align fgModule.right_dual FgModule.rightDual

instance rightRigidCategory : RightRigidCategory (FgModule K) where
#align fgModule.right_rigid_category FgModule.rightRigidCategory

end Field

end FgModule


import tactic

-- Flatten list of pairs
def flatten_pairs : list (ℕ × ℕ) → list ℕ
  | []             := []
  | ((x, y) :: xs) := [x, y] ++ flatten_pairs xs

-- Idea: First construct a function that takes an array of digits and applies "look and say" to it
-- l is stored in reverse order i.e. most recent one is at the front, in the format [(freq, num)]
-- f_aux [] l = l
-- f_aux [1, 1] [(1, 1)] = [(1, 3)]
-- f_aux [1, 1] [(2, 1)] = [(1, 2), (2, 1)]
def look_and_say_aux : list ℕ → list (ℕ × ℕ) → list (ℕ × ℕ)
  | [] l                 := l
  | (x::xs) []           := look_and_say_aux xs [(1, x)]
  | (x::xs) ((f, n)::ls) := look_and_say_aux xs ((ite (x = n) [(f + 1, n)] [(1, x), (f, n)]) ++ ls)

-- Calls look_and_say_aux
-- [1, 1, 2, 3, 3, 1] -> [(1, 1), (3, 2), (2, 1), (1, 2)] ->
def look_and_say : ℕ → list ℕ → list ℕ
  | 0 l       := l
  | (n + 1) l := look_and_say n (flatten_pairs $ list.reverse $ look_and_say_aux l list.nil)

set_option profiler true

-- Test case: Applying the operation 5 time (361ms)
example {l : list ℕ} (h : l = look_and_say 5 [1]) : l = [3, 1, 2, 2, 1, 1] :=
begin
  norm_num [look_and_say, look_and_say_aux, flatten_pairs] at h,
  exact h,
end

-- Test case: Applying the operation 15 times (39.9s)
example {l : list ℕ} (h : l = look_and_say 15 [1]) : l = [1, 3, 2, 1, 1, 3, 2, 1, 3, 2, 2, 1, 1, 3, 3, 1, 1, 2, 1, 3, 2, 1, 1, 3, 3, 1, 1, 2, 1, 1, 1, 3, 1, 2, 2, 1, 1, 2, 1, 3, 2, 1, 1, 3, 1, 2, 1, 1, 1, 3, 2, 2, 2, 1, 1, 2, 3, 1, 1, 3, 1, 1, 2, 2, 2, 1, 1, 3, 1, 1, 1, 2, 3, 1, 1, 3, 3, 2, 1, 1, 1, 2, 1, 3, 2, 1, 1, 3, 2, 2, 2, 1, 1, 3, 1, 2, 1, 1, 3, 2, 1, 1] :=
begin
  norm_num [look_and_say, look_and_say_aux, flatten_pairs] at h,
  exact h,
end

-- TODO (Idea): Prove correspondence between atoms and sequence is actually valid i.e.
--
--          Decompose
-- Sequence --------> Atom
--    |                 |
--    |                 |   look_and_say
--    v                 v
-- Sequence <-------- Atom
--          Map atoms
--
-- Commutes
--
-- Steps:
-- 1. Define (enum?) atoms
-- 2. Prove that if atom1.last != atom2.head, then
--    look_and_say (atom1 || atom2) = look_and_say atom1 || look_and_say atom2
-- 3. Prove the same statement but for the concatenation of multiple atoms (by induction)
-- 4. Prove that look_and_say atom is concatenation of atoms
-- 5. Prove there exists n such that look_and_say n [ 1 ] is eventually always consisting of atoms
-- 6. *Somehow* link this to the adjacency matrix of the graph network and prove the length ratio


/-
Copyright (c) 2020 Alena Gusakov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alena Gusakov, Arthur Paulino, Kyle Miller

! This file was ported from Lean 3 source module combinatorics.simple_graph.matching
! leanprover-community/mathlib commit 138448ae98f529ef34eeb61114191975ee2ca508
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Combinatorics.SimpleGraph.DegreeSum
import Mathlib.Combinatorics.SimpleGraph.Subgraph

/-!
# Matchings

A *matching* for a simple graph is a set of disjoint pairs of adjacent vertices, and the set of all
the vertices in a matching is called its *support* (and sometimes the vertices in the support are
said to be *saturated* by the matching). A *perfect matching* is a matching whose support contains
every vertex of the graph.

In this module, we represent a matching as a subgraph whose vertices are each incident to at most
one edge, and the edges of the subgraph represent the paired vertices.

## Main definitions

* `SimpleGraph.Subgraph.IsMatching`: `M.IsMatching` means that `M` is a matching of its
  underlying graph.
  denoted `M.is_matching`.

* `SimpleGraph.Subgraph.IsPerfectMatching` defines when a subgraph `M` of a simple graph is a
  perfect matching, denoted `M.IsPerfectMatching`.

## TODO

* Define an `other` function and prove useful results about it (https://leanprover.zulipchat.com/#narrow/stream/252551-graph-theory/topic/matchings/near/266205863)

* Provide a bicoloring for matchings (https://leanprover.zulipchat.com/#narrow/stream/252551-graph-theory/topic/matchings/near/265495120)

* Tutte's Theorem

* Hall's Marriage Theorem (see combinatorics.hall)
-/

universe u

namespace SimpleGraph

variable {V : Type u} {G : SimpleGraph V} (M : Subgraph G)

namespace Subgraph

/--
The subgraph `M` of `G` is a matching if every vertex of `M` is incident to exactly one edge in `M`.
We say that the vertices in `M.support` are *matched* or *saturated*.
-/
def IsMatching : Prop := ∀ ⦃v⦄, v ∈ M.verts → ∃! w, M.Adj v w
#align simple_graph.subgraph.is_matching SimpleGraph.Subgraph.IsMatching

/-- Given a vertex, returns the unique edge of the matching it is incident to. -/
noncomputable def IsMatching.toEdge {M : Subgraph G} (h : M.IsMatching) (v : M.verts) : M.edgeSet :=
  ⟨⟦(v, (h v.property).choose)⟧, (h v.property).choose_spec.1⟩
#align simple_graph.subgraph.is_matching.to_edge SimpleGraph.Subgraph.IsMatching.toEdge

theorem IsMatching.toEdge_eq_of_adj {M : Subgraph G} (h : M.IsMatching) {v w : V} (hv : v ∈ M.verts)
    (hvw : M.Adj v w) : h.toEdge ⟨v, hv⟩ = ⟨⟦(v, w)⟧, hvw⟩ := by
  simp only [IsMatching.toEdge, Subtype.mk_eq_mk]
  congr
  exact ((h (M.edge_vert hvw)).choose_spec.2 w hvw).symm
#align simple_graph.subgraph.is_matching.to_edge_eq_of_adj SimpleGraph.Subgraph.IsMatching.toEdge_eq_of_adj

theorem IsMatching.toEdge.surjective {M : Subgraph G} (h : M.IsMatching) :
    Function.Surjective h.toEdge := by
  rintro ⟨e, he⟩
  refine Sym2.ind (fun x y he => ?_) e he
  exact ⟨⟨x, M.edge_vert he⟩, h.toEdge_eq_of_adj _ he⟩
#align simple_graph.subgraph.is_matching.to_edge.surjective SimpleGraph.Subgraph.IsMatching.toEdge.surjective

theorem IsMatching.toEdge_eq_toEdge_of_adj {M : Subgraph G} {v w : V} (h : M.IsMatching)
    (hv : v ∈ M.verts) (hw : w ∈ M.verts) (ha : M.Adj v w) :
    h.toEdge ⟨v, hv⟩ = h.toEdge ⟨w, hw⟩ := by
  rw [h.toEdge_eq_of_adj hv ha, h.toEdge_eq_of_adj hw (M.symm ha), Subtype.mk_eq_mk, Sym2.eq_swap]
#align simple_graph.subgraph.is_matching.to_edge_eq_to_edge_of_adj SimpleGraph.Subgraph.IsMatching.toEdge_eq_toEdge_of_adj

/--
The subgraph `M` of `G` is a perfect matching on `G` if it's a matching and every vertex `G` is
matched.
-/
def IsPerfectMatching : Prop := M.IsMatching ∧ M.IsSpanning
#align simple_graph.subgraph.is_perfect_matching SimpleGraph.Subgraph.IsPerfectMatching

theorem IsMatching.support_eq_verts {M : Subgraph G} (h : M.IsMatching) : M.support = M.verts := by
  refine M.support_subset_verts.antisymm fun v hv => ?_
  obtain ⟨w, hvw, -⟩ := h hv
  exact ⟨_, hvw⟩
#align simple_graph.subgraph.is_matching.support_eq_verts SimpleGraph.Subgraph.IsMatching.support_eq_verts

theorem isMatching_iff_forall_degree {M : Subgraph G} [∀ v : V, Fintype (M.neighborSet v)] :
    M.IsMatching ↔ ∀ v : V, v ∈ M.verts → M.degree v = 1 := by
  simp only [degree_eq_one_iff_unique_adj, IsMatching]
#align simple_graph.subgraph.is_matching_iff_forall_degree SimpleGraph.Subgraph.isMatching_iff_forall_degree

theorem IsMatching.even_card {M : Subgraph G} [Fintype M.verts] (h : M.IsMatching) :
    Even M.verts.toFinset.card := by
  classical
  rw [isMatching_iff_forall_degree] at h
  use M.coe.edgeFinset.card
  rw [← two_mul, ← M.coe.sum_degrees_eq_twice_card_edges]
  -- Porting note: `SimpleGraph.Subgraph.coe_degree` does not trigger because it uses
  -- instance arguments instead of implicit arguments for the first `Fintype` argument.
  -- Using a `convert_to` to swap out the `Fintype` instance to the "right" one.
  convert_to _ = Finset.sum Finset.univ fun v => SimpleGraph.degree (Subgraph.coe M) v using 3
  simp [h, Finset.card_univ]
#align simple_graph.subgraph.is_matching.even_card SimpleGraph.Subgraph.IsMatching.even_card

theorem isPerfectMatching_iff : M.IsPerfectMatching ↔ ∀ v, ∃! w, M.Adj v w := by
  refine' ⟨_, fun hm => ⟨fun v _ => hm v, fun v => _⟩⟩
  · rintro ⟨hm, hs⟩ v
    exact hm (hs v)
  · obtain ⟨w, hw, -⟩ := hm v
    exact M.edge_vert hw
#align simple_graph.subgraph.is_perfect_matching_iff SimpleGraph.Subgraph.isPerfectMatching_iff

theorem isPerfectMatching_iff_forall_degree {M : Subgraph G} [∀ v, Fintype (M.neighborSet v)] :
    M.IsPerfectMatching ↔ ∀ v, M.degree v = 1 := by
  simp [degree_eq_one_iff_unique_adj, isPerfectMatching_iff]
#align simple_graph.subgraph.is_perfect_matching_iff_forall_degree SimpleGraph.Subgraph.isPerfectMatching_iff_forall_degree

theorem IsPerfectMatching.even_card {M : Subgraph G} [Fintype V] (h : M.IsPerfectMatching) :
    Even (Fintype.card V) := by
  classical
  simpa only [h.2.card_verts] using IsMatching.even_card h.1
#align simple_graph.subgraph.is_perfect_matching.even_card SimpleGraph.Subgraph.IsPerfectMatching.even_card

end Subgraph

end SimpleGraph


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

! This file was ported from Lean 3 source module dynamics.flow
! leanprover-community/mathlib commit ef7acf407d265ad4081c8998687e994fa80ba70c
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Topology.Algebra.Group.Basic
import Mathbin.Logic.Function.Iterate

/-!
# Flows and invariant sets

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

This file defines a flow on a topological space `α` by a topological
monoid `τ` as a continuous monoid-act of `τ` on `α`. Anticipating the
cases where `τ` is one of `ℕ`, `ℤ`, `ℝ⁺`, or `ℝ`, we use additive
notation for the monoids, though the definition does not require
commutativity.

A subset `s` of `α` is invariant under a family of maps `ϕₜ : α → α`
if `ϕₜ s ⊆ s` for all `t`. In many cases `ϕ` will be a flow on
`α`. For the cases where `ϕ` is a flow by an ordered (additive,
commutative) monoid, we additionally define forward invariance, where
`t` ranges over those elements which are nonnegative.

Additionally, we define such constructions as the restriction of a
flow onto an invariant subset, and the time-reveral of a flow by a
group.
-/

open Set Function Filter

/-!
### Invariant sets
-/

section Invariant

variable {τ : Type _} {α : Type _}

#print IsInvariant /-
/-- A set `s ⊆ α` is invariant under `ϕ : τ → α → α` if
    `ϕ t s ⊆ s` for all `t` in `τ`. -/
def IsInvariant (ϕ : τ → α → α) (s : Set α) : Prop :=
  ∀ t, MapsTo (ϕ t) s s
#align is_invariant IsInvariant
-/

variable (ϕ : τ → α → α) (s : Set α)

/- warning: is_invariant_iff_image -> isInvariant_iff_image is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} {α : Type.{u2}} (ϕ : τ -> α -> α) (s : Set.{u2} α), Iff (IsInvariant.{u1, u2} τ α ϕ s) (forall (t : τ), HasSubset.Subset.{u2} (Set.{u2} α) (Set.hasSubset.{u2} α) (Set.image.{u2, u2} α α (ϕ t) s) s)
but is expected to have type
  forall {τ : Type.{u2}} {α : Type.{u1}} (ϕ : τ -> α -> α) (s : Set.{u1} α), Iff (IsInvariant.{u2, u1} τ α ϕ s) (forall (t : τ), HasSubset.Subset.{u1} (Set.{u1} α) (Set.instHasSubsetSet.{u1} α) (Set.image.{u1, u1} α α (ϕ t) s) s)
Case conversion may be inaccurate. Consider using '#align is_invariant_iff_image isInvariant_iff_imageₓ'. -/
theorem isInvariant_iff_image : IsInvariant ϕ s ↔ ∀ t, ϕ t '' s ⊆ s := by
  simp_rw [IsInvariant, maps_to']
#align is_invariant_iff_image isInvariant_iff_image

#print IsFwInvariant /-
/-- A set `s ⊆ α` is forward-invariant under `ϕ : τ → α → α` if
    `ϕ t s ⊆ s` for all `t ≥ 0`. -/
def IsFwInvariant [Preorder τ] [Zero τ] (ϕ : τ → α → α) (s : Set α) : Prop :=
  ∀ ⦃t⦄, 0 ≤ t → MapsTo (ϕ t) s s
#align is_fw_invariant IsFwInvariant
-/

/- warning: is_invariant.is_fw_invariant -> IsInvariant.isFwInvariant is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} {α : Type.{u2}} [_inst_1 : Preorder.{u1} τ] [_inst_2 : Zero.{u1} τ] {ϕ : τ -> α -> α} {s : Set.{u2} α}, (IsInvariant.{u1, u2} τ α ϕ s) -> (IsFwInvariant.{u1, u2} τ α _inst_1 _inst_2 ϕ s)
but is expected to have type
  forall {τ : Type.{u2}} {α : Type.{u1}} [_inst_1 : Preorder.{u2} τ] [_inst_2 : Zero.{u2} τ] {ϕ : τ -> α -> α} {s : Set.{u1} α}, (IsInvariant.{u2, u1} τ α ϕ s) -> (IsFwInvariant.{u2, u1} τ α _inst_1 _inst_2 ϕ s)
Case conversion may be inaccurate. Consider using '#align is_invariant.is_fw_invariant IsInvariant.isFwInvariantₓ'. -/
theorem IsInvariant.isFwInvariant [Preorder τ] [Zero τ] {ϕ : τ → α → α} {s : Set α}
    (h : IsInvariant ϕ s) : IsFwInvariant ϕ s := fun t ht => h t
#align is_invariant.is_fw_invariant IsInvariant.isFwInvariant

/- warning: is_fw_invariant.is_invariant -> IsFwInvariant.isInvariant is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} {α : Type.{u2}} [_inst_1 : CanonicallyOrderedAddMonoid.{u1} τ] {ϕ : τ -> α -> α} {s : Set.{u2} α}, (IsFwInvariant.{u1, u2} τ α (PartialOrder.toPreorder.{u1} τ (OrderedAddCommMonoid.toPartialOrder.{u1} τ (CanonicallyOrderedAddMonoid.toOrderedAddCommMonoid.{u1} τ _inst_1))) (AddZeroClass.toHasZero.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ (AddCommMonoid.toAddMonoid.{u1} τ (OrderedAddCommMonoid.toAddCommMonoid.{u1} τ (CanonicallyOrderedAddMonoid.toOrderedAddCommMonoid.{u1} τ _inst_1))))) ϕ s) -> (IsInvariant.{u1, u2} τ α ϕ s)
but is expected to have type
  forall {τ : Type.{u2}} {α : Type.{u1}} [_inst_1 : CanonicallyOrderedAddMonoid.{u2} τ] {ϕ : τ -> α -> α} {s : Set.{u1} α}, (IsFwInvariant.{u2, u1} τ α (PartialOrder.toPreorder.{u2} τ (OrderedAddCommMonoid.toPartialOrder.{u2} τ (CanonicallyOrderedAddMonoid.toOrderedAddCommMonoid.{u2} τ _inst_1))) (AddMonoid.toZero.{u2} τ (AddCommMonoid.toAddMonoid.{u2} τ (OrderedAddCommMonoid.toAddCommMonoid.{u2} τ (CanonicallyOrderedAddMonoid.toOrderedAddCommMonoid.{u2} τ _inst_1)))) ϕ s) -> (IsInvariant.{u2, u1} τ α ϕ s)
Case conversion may be inaccurate. Consider using '#align is_fw_invariant.is_invariant IsFwInvariant.isInvariantₓ'. -/
/-- If `τ` is a `canonically_ordered_add_monoid` (e.g., `ℕ` or `ℝ≥0`), then the notions
`is_fw_invariant` and `is_invariant` are equivalent. -/
theorem IsFwInvariant.isInvariant [CanonicallyOrderedAddMonoid τ] {ϕ : τ → α → α} {s : Set α}
    (h : IsFwInvariant ϕ s) : IsInvariant ϕ s := fun t => h (zero_le t)
#align is_fw_invariant.is_invariant IsFwInvariant.isInvariant

/- warning: is_fw_invariant_iff_is_invariant -> isFwInvariant_iff_isInvariant is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} {α : Type.{u2}} [_inst_1 : CanonicallyOrderedAddMonoid.{u1} τ] {ϕ : τ -> α -> α} {s : Set.{u2} α}, Iff (IsFwInvariant.{u1, u2} τ α (PartialOrder.toPreorder.{u1} τ (OrderedAddCommMonoid.toPartialOrder.{u1} τ (CanonicallyOrderedAddMonoid.toOrderedAddCommMonoid.{u1} τ _inst_1))) (AddZeroClass.toHasZero.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ (AddCommMonoid.toAddMonoid.{u1} τ (OrderedAddCommMonoid.toAddCommMonoid.{u1} τ (CanonicallyOrderedAddMonoid.toOrderedAddCommMonoid.{u1} τ _inst_1))))) ϕ s) (IsInvariant.{u1, u2} τ α ϕ s)
but is expected to have type
  forall {τ : Type.{u2}} {α : Type.{u1}} [_inst_1 : CanonicallyOrderedAddMonoid.{u2} τ] {ϕ : τ -> α -> α} {s : Set.{u1} α}, Iff (IsFwInvariant.{u2, u1} τ α (PartialOrder.toPreorder.{u2} τ (OrderedAddCommMonoid.toPartialOrder.{u2} τ (CanonicallyOrderedAddMonoid.toOrderedAddCommMonoid.{u2} τ _inst_1))) (AddMonoid.toZero.{u2} τ (AddCommMonoid.toAddMonoid.{u2} τ (OrderedAddCommMonoid.toAddCommMonoid.{u2} τ (CanonicallyOrderedAddMonoid.toOrderedAddCommMonoid.{u2} τ _inst_1)))) ϕ s) (IsInvariant.{u2, u1} τ α ϕ s)
Case conversion may be inaccurate. Consider using '#align is_fw_invariant_iff_is_invariant isFwInvariant_iff_isInvariantₓ'. -/
/-- If `τ` is a `canonically_ordered_add_monoid` (e.g., `ℕ` or `ℝ≥0`), then the notions
`is_fw_invariant` and `is_invariant` are equivalent. -/
theorem isFwInvariant_iff_isInvariant [CanonicallyOrderedAddMonoid τ] {ϕ : τ → α → α} {s : Set α} :
    IsFwInvariant ϕ s ↔ IsInvariant ϕ s :=
  ⟨IsFwInvariant.isInvariant, IsInvariant.isFwInvariant⟩
#align is_fw_invariant_iff_is_invariant isFwInvariant_iff_isInvariant

end Invariant

/-!
### Flows
-/

/- warning: flow -> Flow is a dubious translation:
lean 3 declaration is
  forall (τ : Type.{u1}) [_inst_1 : TopologicalSpace.{u1} τ] [_inst_2 : AddMonoid.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_1 (AddZeroClass.toHasAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_2))] (α : Type.{u2}) [_inst_4 : TopologicalSpace.{u2} α], Sort.{max (succ u1) (succ u2)}
but is expected to have type
  forall (τ : Type.{u1}) [_inst_1 : TopologicalSpace.{u1} τ] [_inst_2 : AddMonoid.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_1 (AddZeroClass.toAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_2))] (α : Type.{u2}) [_inst_4 : TopologicalSpace.{u2} α], Sort.{max (succ u1) (succ u2)}
Case conversion may be inaccurate. Consider using '#align flow Flowₓ'. -/
/-- A flow on a topological space `α` by an a additive topological
    monoid `τ` is a continuous monoid action of `τ` on `α`.-/
structure Flow (τ : Type _) [TopologicalSpace τ] [AddMonoid τ] [ContinuousAdd τ] (α : Type _)
  [TopologicalSpace α] where
  toFun : τ → α → α
  cont' : Continuous (uncurry to_fun)
  map_add' : ∀ t₁ t₂ x, to_fun (t₁ + t₂) x = to_fun t₁ (to_fun t₂ x)
  map_zero' : ∀ x, to_fun 0 x = x
#align flow Flow

namespace Flow

variable {τ : Type _} [AddMonoid τ] [TopologicalSpace τ] [ContinuousAdd τ] {α : Type _}
  [TopologicalSpace α] (ϕ : Flow τ α)

instance : Inhabited (Flow τ α) :=
  ⟨{  toFun := fun _ x => x
      cont' := continuous_snd
      map_add' := fun _ _ _ => rfl
      map_zero' := fun _ => rfl }⟩

instance : CoeFun (Flow τ α) fun _ => τ → α → α :=
  ⟨Flow.toFun⟩

/- warning: flow.ext -> Flow.ext is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} [_inst_1 : AddMonoid.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_2 (AddZeroClass.toHasAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] {ϕ₁ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4} {ϕ₂ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4}, (forall (t : τ) (x : α), Eq.{succ u2} α (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ _inst_1 _inst_2 _inst_3 α _inst_4) ϕ₁ t x) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ _inst_1 _inst_2 _inst_3 α _inst_4) ϕ₂ t x)) -> (Eq.{max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) ϕ₁ ϕ₂)
but is expected to have type
  forall {τ : Type.{u2}} [_inst_1 : AddMonoid.{u2} τ] [_inst_2 : TopologicalSpace.{u2} τ] [_inst_3 : ContinuousAdd.{u2} τ _inst_2 (AddZeroClass.toAdd.{u2} τ (AddMonoid.toAddZeroClass.{u2} τ _inst_1))] {α : Type.{u1}} [_inst_4 : TopologicalSpace.{u1} α] {ϕ₁ : Flow.{u2, u1} τ _inst_2 _inst_1 _inst_3 α _inst_4} {ϕ₂ : Flow.{u2, u1} τ _inst_2 _inst_1 _inst_3 α _inst_4}, (forall (t : τ) (x : α), Eq.{succ u1} α (Flow.toFun.{u2, u1} τ _inst_2 _inst_1 _inst_3 α _inst_4 ϕ₁ t x) (Flow.toFun.{u2, u1} τ _inst_2 _inst_1 _inst_3 α _inst_4 ϕ₂ t x)) -> (Eq.{max (succ u2) (succ u1)} (Flow.{u2, u1} τ _inst_2 _inst_1 _inst_3 α _inst_4) ϕ₁ ϕ₂)
Case conversion may be inaccurate. Consider using '#align flow.ext Flow.extₓ'. -/
@[ext]
theorem ext : ∀ {ϕ₁ ϕ₂ : Flow τ α}, (∀ t x, ϕ₁ t x = ϕ₂ t x) → ϕ₁ = ϕ₂
  | ⟨f₁, _, _, _⟩, ⟨f₂, _, _, _⟩, h => by
    congr
    funext
    exact h _ _
#align flow.ext Flow.ext

/- warning: flow.continuous -> Flow.continuous is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} [_inst_1 : AddMonoid.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_2 (AddZeroClass.toHasAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) {β : Type.{u3}} [_inst_5 : TopologicalSpace.{u3} β] {t : β -> τ}, (Continuous.{u3, u1} β τ _inst_5 _inst_2 t) -> (forall {f : β -> α}, (Continuous.{u3, u2} β α _inst_5 _inst_4 f) -> (Continuous.{u3, u2} β α _inst_5 _inst_4 (fun (x : β) => coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ _inst_1 _inst_2 _inst_3 α _inst_4) ϕ (t x) (f x))))
but is expected to have type
  forall {τ : Type.{u2}} [_inst_1 : AddMonoid.{u2} τ] [_inst_2 : TopologicalSpace.{u2} τ] [_inst_3 : ContinuousAdd.{u2} τ _inst_2 (AddZeroClass.toAdd.{u2} τ (AddMonoid.toAddZeroClass.{u2} τ _inst_1))] {α : Type.{u1}} [_inst_4 : TopologicalSpace.{u1} α] (ϕ : Flow.{u2, u1} τ _inst_2 _inst_1 _inst_3 α _inst_4) {β : Type.{u3}} [_inst_5 : TopologicalSpace.{u3} β] {t : β -> τ}, (Continuous.{u3, u2} β τ _inst_5 _inst_2 t) -> (forall {f : β -> α}, (Continuous.{u3, u1} β α _inst_5 _inst_4 f) -> (Continuous.{u3, u1} β α _inst_5 _inst_4 (fun (x : β) => Flow.toFun.{u2, u1} τ _inst_2 _inst_1 _inst_3 α _inst_4 ϕ (t x) (f x))))
Case conversion may be inaccurate. Consider using '#align flow.continuous Flow.continuousₓ'. -/
@[continuity]
protected theorem continuous {β : Type _} [TopologicalSpace β] {t : β → τ} (ht : Continuous t)
    {f : β → α} (hf : Continuous f) : Continuous fun x => ϕ (t x) (f x) :=
  ϕ.cont'.comp (ht.prod_mk hf)
#align flow.continuous Flow.continuous

/- warning: continuous.flow -> Continuous.flow is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} [_inst_1 : AddMonoid.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_2 (AddZeroClass.toHasAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) {β : Type.{u3}} [_inst_5 : TopologicalSpace.{u3} β] {t : β -> τ}, (Continuous.{u3, u1} β τ _inst_5 _inst_2 t) -> (forall {f : β -> α}, (Continuous.{u3, u2} β α _inst_5 _inst_4 f) -> (Continuous.{u3, u2} β α _inst_5 _inst_4 (fun (x : β) => coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ _inst_1 _inst_2 _inst_3 α _inst_4) ϕ (t x) (f x))))
but is expected to have type
  forall {τ : Type.{u2}} [_inst_1 : AddMonoid.{u2} τ] [_inst_2 : TopologicalSpace.{u2} τ] [_inst_3 : ContinuousAdd.{u2} τ _inst_2 (AddZeroClass.toAdd.{u2} τ (AddMonoid.toAddZeroClass.{u2} τ _inst_1))] {α : Type.{u1}} [_inst_4 : TopologicalSpace.{u1} α] (ϕ : Flow.{u2, u1} τ _inst_2 _inst_1 _inst_3 α _inst_4) {β : Type.{u3}} [_inst_5 : TopologicalSpace.{u3} β] {t : β -> τ}, (Continuous.{u3, u2} β τ _inst_5 _inst_2 t) -> (forall {f : β -> α}, (Continuous.{u3, u1} β α _inst_5 _inst_4 f) -> (Continuous.{u3, u1} β α _inst_5 _inst_4 (fun (x : β) => Flow.toFun.{u2, u1} τ _inst_2 _inst_1 _inst_3 α _inst_4 ϕ (t x) (f x))))
Case conversion may be inaccurate. Consider using '#align continuous.flow Continuous.flowₓ'. -/
alias Flow.continuous ← _root_.continuous.flow
#align continuous.flow Continuous.flow

/- warning: flow.map_add -> Flow.map_add is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} [_inst_1 : AddMonoid.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_2 (AddZeroClass.toHasAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (t₁ : τ) (t₂ : τ) (x : α), Eq.{succ u2} α (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ _inst_1 _inst_2 _inst_3 α _inst_4) ϕ (HAdd.hAdd.{u1, u1, u1} τ τ τ (instHAdd.{u1} τ (AddZeroClass.toHasAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))) t₁ t₂) x) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ _inst_1 _inst_2 _inst_3 α _inst_4) ϕ t₁ (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ _inst_1 _inst_2 _inst_3 α _inst_4) ϕ t₂ x))
but is expected to have type
  forall {τ : Type.{u1}} [_inst_1 : AddMonoid.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_2 (AddZeroClass.toAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (t₁ : τ) (t₂ : τ) (x : α), Eq.{succ u2} α (Flow.toFun.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4 ϕ (HAdd.hAdd.{u1, u1, u1} τ τ τ (instHAdd.{u1} τ (AddZeroClass.toAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))) t₁ t₂) x) (Flow.toFun.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4 ϕ t₁ (Flow.toFun.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4 ϕ t₂ x))
Case conversion may be inaccurate. Consider using '#align flow.map_add Flow.map_addₓ'. -/
theorem map_add (t₁ t₂ : τ) (x : α) : ϕ (t₁ + t₂) x = ϕ t₁ (ϕ t₂ x) :=
  ϕ.map_add' _ _ _
#align flow.map_add Flow.map_add

/- warning: flow.map_zero -> Flow.map_zero is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} [_inst_1 : AddMonoid.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_2 (AddZeroClass.toHasAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4), Eq.{succ u2} (α -> α) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ _inst_1 _inst_2 _inst_3 α _inst_4) ϕ (OfNat.ofNat.{u1} τ 0 (OfNat.mk.{u1} τ 0 (Zero.zero.{u1} τ (AddZeroClass.toHasZero.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1)))))) (id.{succ u2} α)
but is expected to have type
  forall {τ : Type.{u1}} [_inst_1 : AddMonoid.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_2 (AddZeroClass.toAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4), Eq.{succ u2} (α -> α) (Flow.toFun.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4 ϕ (OfNat.ofNat.{u1} τ 0 (Zero.toOfNat0.{u1} τ (AddMonoid.toZero.{u1} τ _inst_1)))) (id.{succ u2} α)
Case conversion may be inaccurate. Consider using '#align flow.map_zero Flow.map_zeroₓ'. -/
@[simp]
theorem map_zero : ϕ 0 = id :=
  funext ϕ.map_zero'
#align flow.map_zero Flow.map_zero

/- warning: flow.map_zero_apply -> Flow.map_zero_apply is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} [_inst_1 : AddMonoid.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_2 (AddZeroClass.toHasAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (x : α), Eq.{succ u2} α (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ _inst_1 _inst_2 _inst_3 α _inst_4) ϕ (OfNat.ofNat.{u1} τ 0 (OfNat.mk.{u1} τ 0 (Zero.zero.{u1} τ (AddZeroClass.toHasZero.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))))) x) x
but is expected to have type
  forall {τ : Type.{u1}} [_inst_1 : AddMonoid.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_2 (AddZeroClass.toAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (x : α), Eq.{succ u2} α (Flow.toFun.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4 ϕ (OfNat.ofNat.{u1} τ 0 (Zero.toOfNat0.{u1} τ (AddMonoid.toZero.{u1} τ _inst_1))) x) x
Case conversion may be inaccurate. Consider using '#align flow.map_zero_apply Flow.map_zero_applyₓ'. -/
theorem map_zero_apply (x : α) : ϕ 0 x = x :=
  ϕ.map_zero' x
#align flow.map_zero_apply Flow.map_zero_apply

/- warning: flow.from_iter -> Flow.fromIter is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_4 : TopologicalSpace.{u1} α] {g : α -> α}, (Continuous.{u1, u1} α α _inst_4 _inst_4 g) -> (Flow.{0, u1} Nat Nat.topologicalSpace Nat.addMonoid Flow.fromIter._proof_1 α _inst_4)
but is expected to have type
  forall {α : Type.{u1}} [_inst_4 : TopologicalSpace.{u1} α] {g : α -> α}, (Continuous.{u1, u1} α α _inst_4 _inst_4 g) -> (Flow.{0, u1} Nat instTopologicalSpaceNat Nat.addMonoid (continuousAdd_of_discreteTopology.{0} Nat instTopologicalSpaceNat (AddZeroClass.toAdd.{0} Nat (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) instDiscreteTopologyNatInstTopologicalSpaceNat) α _inst_4)
Case conversion may be inaccurate. Consider using '#align flow.from_iter Flow.fromIterₓ'. -/
/-- Iterations of a continuous function from a topological space `α`
    to itself defines a semiflow by `ℕ` on `α`. -/
def fromIter {g : α → α} (h : Continuous g) : Flow ℕ α
    where
  toFun n x := (g^[n]) x
  cont' := continuous_uncurry_of_discreteTopology_left (Continuous.iterate h)
  map_add' := iterate_add_apply _
  map_zero' x := rfl
#align flow.from_iter Flow.fromIter

/- warning: flow.restrict -> Flow.restrict is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} [_inst_1 : AddMonoid.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_2 (AddZeroClass.toHasAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) {s : Set.{u2} α}, (IsInvariant.{u1, u2} τ α (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ _inst_1 _inst_2 _inst_3 α _inst_4) ϕ) s) -> (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 (coeSort.{succ u2, succ (succ u2)} (Set.{u2} α) Type.{u2} (Set.hasCoeToSort.{u2} α) s) (Subtype.topologicalSpace.{u2} α (fun (x : α) => Membership.Mem.{u2, u2} α (Set.{u2} α) (Set.hasMem.{u2} α) x s) _inst_4))
but is expected to have type
  forall {τ : Type.{u1}} [_inst_1 : AddMonoid.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : ContinuousAdd.{u1} τ _inst_2 (AddZeroClass.toAdd.{u1} τ (AddMonoid.toAddZeroClass.{u1} τ _inst_1))] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4) {s : Set.{u2} α}, (IsInvariant.{u1, u2} τ α (Flow.toFun.{u1, u2} τ _inst_2 _inst_1 _inst_3 α _inst_4 ϕ) s) -> (Flow.{u1, u2} τ _inst_2 _inst_1 _inst_3 (Set.Elem.{u2} α s) (instTopologicalSpaceSubtype.{u2} α (fun (x : α) => Membership.mem.{u2, u2} α (Set.{u2} α) (Set.instMembershipSet.{u2} α) x s) _inst_4))
Case conversion may be inaccurate. Consider using '#align flow.restrict Flow.restrictₓ'. -/
/-- Restriction of a flow onto an invariant set. -/
def restrict {s : Set α} (h : IsInvariant ϕ s) : Flow τ ↥s
    where
  toFun t := (h t).restrict _ _ _
  cont' := (ϕ.Continuous continuous_fst continuous_subtype_val.snd').subtype_mk _
  map_add' _ _ _ := Subtype.ext (map_add _ _ _ _)
  map_zero' _ := Subtype.ext (map_zero_apply _ _)
#align flow.restrict Flow.restrict

end Flow

namespace Flow

variable {τ : Type _} [AddCommGroup τ] [TopologicalSpace τ] [TopologicalAddGroup τ] {α : Type _}
  [TopologicalSpace α] (ϕ : Flow τ α)

/- warning: flow.is_invariant_iff_image_eq -> Flow.isInvariant_iff_image_eq is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} [_inst_1 : AddCommGroup.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : TopologicalAddGroup.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1)] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) (s : Set.{u2} α), Iff (IsInvariant.{u1, u2} τ α (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) _inst_2 (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) ϕ) s) (forall (t : τ), Eq.{succ u2} (Set.{u2} α) (Set.image.{u2, u2} α α (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) _inst_2 (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) ϕ t) s) s)
but is expected to have type
  forall {τ : Type.{u1}} [_inst_1 : AddCommGroup.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : TopologicalAddGroup.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1)] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.toContinuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) (s : Set.{u2} α), Iff (IsInvariant.{u1, u2} τ α (Flow.toFun.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.toContinuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4 ϕ) s) (forall (t : τ), Eq.{succ u2} (Set.{u2} α) (Set.image.{u2, u2} α α (Flow.toFun.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.toContinuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4 ϕ t) s) s)
Case conversion may be inaccurate. Consider using '#align flow.is_invariant_iff_image_eq Flow.isInvariant_iff_image_eqₓ'. -/
theorem isInvariant_iff_image_eq (s : Set α) : IsInvariant ϕ s ↔ ∀ t, ϕ t '' s = s :=
  (isInvariant_iff_image _ _).trans
    (Iff.intro
      (fun h t => Subset.antisymm (h t) fun _ hx => ⟨_, h (-t) ⟨_, hx, rfl⟩, by simp [← map_add]⟩)
      fun h t => by rw [h t])
#align flow.is_invariant_iff_image_eq Flow.isInvariant_iff_image_eq

/- warning: flow.reverse -> Flow.reverse is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} [_inst_1 : AddCommGroup.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : TopologicalAddGroup.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1)] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α], (Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (Flow.reverse._proof_1.{u1} τ _inst_1 _inst_2 _inst_3) α _inst_4) -> (Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (Flow.reverse._proof_2.{u1} τ _inst_1 _inst_2 _inst_3) α _inst_4)
but is expected to have type
  forall {τ : Type.{u1}} [_inst_1 : AddCommGroup.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : TopologicalAddGroup.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1)] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α], (Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.toContinuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) -> (Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.toContinuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4)
Case conversion may be inaccurate. Consider using '#align flow.reverse Flow.reverseₓ'. -/
/-- The time-reversal of a flow `ϕ` by a (commutative, additive) group
    is defined `ϕ.reverse t x = ϕ (-t) x`. -/
def reverse : Flow τ α where
  toFun t := ϕ (-t)
  cont' := ϕ.Continuous continuous_fst.neg continuous_snd
  map_add' _ _ _ := by rw [neg_add, map_add]
  map_zero' _ := by rw [neg_zero, map_zero_apply]
#align flow.reverse Flow.reverse

/- warning: flow.to_homeomorph -> Flow.toHomeomorph is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} [_inst_1 : AddCommGroup.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : TopologicalAddGroup.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1)] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α], (Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (Flow.toHomeomorph._proof_1.{u1} τ _inst_1 _inst_2 _inst_3) α _inst_4) -> τ -> (Homeomorph.{u2, u2} α α _inst_4 _inst_4)
but is expected to have type
  forall {τ : Type.{u1}} [_inst_1 : AddCommGroup.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : TopologicalAddGroup.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1)] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α], (Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.toContinuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) -> τ -> (Homeomorph.{u2, u2} α α _inst_4 _inst_4)
Case conversion may be inaccurate. Consider using '#align flow.to_homeomorph Flow.toHomeomorphₓ'. -/
/-- The map `ϕ t` as a homeomorphism. -/
def toHomeomorph (t : τ) : α ≃ₜ α where
  toFun := ϕ t
  invFun := ϕ (-t)
  left_inv x := by rw [← map_add, neg_add_self, map_zero_apply]
  right_inv x := by rw [← map_add, add_neg_self, map_zero_apply]
#align flow.to_homeomorph Flow.toHomeomorph

/- warning: flow.image_eq_preimage -> Flow.image_eq_preimage is a dubious translation:
lean 3 declaration is
  forall {τ : Type.{u1}} [_inst_1 : AddCommGroup.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : TopologicalAddGroup.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1)] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) (t : τ) (s : Set.{u2} α), Eq.{succ u2} (Set.{u2} α) (Set.image.{u2, u2} α α (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) _inst_2 (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) ϕ t) s) (Set.preimage.{u2, u2} α α (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) (fun (_x : Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) => τ -> α -> α) (Flow.hasCoeToFun.{u1, u2} τ (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) _inst_2 (TopologicalAddGroup.to_continuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) ϕ (Neg.neg.{u1} τ (SubNegMonoid.toHasNeg.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) t)) s)
but is expected to have type
  forall {τ : Type.{u1}} [_inst_1 : AddCommGroup.{u1} τ] [_inst_2 : TopologicalSpace.{u1} τ] [_inst_3 : TopologicalAddGroup.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1)] {α : Type.{u2}} [_inst_4 : TopologicalSpace.{u2} α] (ϕ : Flow.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.toContinuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4) (t : τ) (s : Set.{u2} α), Eq.{succ u2} (Set.{u2} α) (Set.image.{u2, u2} α α (Flow.toFun.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.toContinuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4 ϕ t) s) (Set.preimage.{u2, u2} α α (Flow.toFun.{u1, u2} τ _inst_2 (SubNegMonoid.toAddMonoid.{u1} τ (AddGroup.toSubNegMonoid.{u1} τ (AddCommGroup.toAddGroup.{u1} τ _inst_1))) (TopologicalAddGroup.toContinuousAdd.{u1} τ _inst_2 (AddCommGroup.toAddGroup.{u1} τ _inst_1) _inst_3) α _inst_4 ϕ (Neg.neg.{u1} τ (NegZeroClass.toNeg.{u1} τ (SubNegZeroMonoid.toNegZeroClass.{u1} τ (SubtractionMonoid.toSubNegZeroMonoid.{u1} τ (SubtractionCommMonoid.toSubtractionMonoid.{u1} τ (AddCommGroup.toDivisionAddCommMonoid.{u1} τ _inst_1))))) t)) s)
Case conversion may be inaccurate. Consider using '#align flow.image_eq_preimage Flow.image_eq_preimageₓ'. -/
theorem image_eq_preimage (t : τ) (s : Set α) : ϕ t '' s = ϕ (-t) ⁻¹' s :=
  (ϕ.toHomeomorph t).toEquiv.image_eq_preimage s
#align flow.image_eq_preimage Flow.image_eq_preimage

end Flow


/-
Copyright (c) 2021 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import analysis.box_integral.basic
import analysis.box_integral.partition.additive
import analysis.calculus.fderiv

/-!
# Divergence integral for Henstock-Kurzweil integral

In this file we prove the Divergence Theorem for a Henstock-Kurzweil style integral. The theorem
says the following. Let `f : ℝⁿ → Eⁿ` be a function differentiable on a closed rectangular box
`I` with derivative `f' x : ℝⁿ →L[ℝ] Eⁿ` at `x ∈ I`. Then the divergence `λ x, ∑ k, f' x eₖ k`,
where `eₖ = pi.single k 1` is the `k`-th basis vector, is integrable on `I`, and its integral is
equal to the sum of integrals of `f` over the faces of `I` taken with appropriate signs.

To make the proof work, we had to ban tagged partitions with “long and thin” boxes. More precisely,
we use the following generalization of one-dimensional Henstock-Kurzweil integral to functions
defined on a box in `ℝⁿ` (it corresponds to the value `⊥` of `box_integral.integration_params` in
the definition of `box_integral.has_integral`).

We say that `f : ℝⁿ → E` has integral `y : E` over a box `I ⊆ ℝⁿ` if for an arbitrarily small
positive `ε` and an arbitrarily large `c`, there exists a function `r : ℝⁿ → (0, ∞)` such that for
any tagged partition `π` of `I` such that

* `π` is a Henstock partition, i.e., each tag belongs to its box;
* `π` is subordinate to `r`;
* for every box of `π`, the maximum of the ratios of its sides is less than or equal to `c`,

the integral sum of `f` over `π` is `ε`-close to `y`. In case of dimension one, the last condition
trivially holds for any `c ≥ 1`, so this definition is equivalent to the standard definition of
Henstock-Kurzweil integral.

## Tags

Henstock-Kurzweil integral, integral, Stokes theorem, divergence theorem
-/

open_locale classical big_operators nnreal ennreal topological_space box_integral
open continuous_linear_map (lsmul) filter set finset metric
noncomputable theory

universes u
variables {E : Type u} [normed_group E] [normed_space ℝ E] {n : ℕ}

namespace box_integral

local notation `ℝⁿ` := fin n → ℝ
local notation `ℝⁿ⁺¹` := fin (n + 1) → ℝ
local notation `Eⁿ⁺¹` := fin (n + 1) → E

variables [complete_space E] (I : box (fin (n + 1))) {i : fin (n + 1)}

open measure_theory

/-- Auxiliary lemma for the divergence theorem. -/
lemma norm_volume_sub_integral_face_upper_sub_lower_smul_le
  {f : ℝⁿ⁺¹ → E} {f' : ℝⁿ⁺¹ →L[ℝ] E} (hfc : continuous_on f I.Icc)
  {x : ℝⁿ⁺¹} (hxI : x ∈ I.Icc) {a : E} {ε : ℝ} (h0 : 0 < ε)
  (hε : ∀ y ∈ I.Icc, ∥f y - a - f' (y - x)∥ ≤ ε * ∥y - x∥) {c : ℝ≥0} (hc : I.distortion ≤ c) :
  ∥(∏ j, (I.upper j - I.lower j)) • f' (pi.single i 1) -
    (integral (I.face i) ⊥ (f ∘ i.insert_nth (I.upper i)) box_additive_map.volume -
      integral (I.face i) ⊥ (f ∘ i.insert_nth (I.lower i)) box_additive_map.volume)∥ ≤
    2 * ε * c * ∏ j, (I.upper j - I.lower j) :=
begin
  /- **Plan of the proof**. The difference of the integrals of the affine function
  `λ y, a + f' (y - x)` over the faces `x i = I.upper i` and `x i = I.lower i` is equal to the
  volume of `I` multiplied by `f' (pi.single i 1)`, so it suffices to show that the integral of
  `f y - a - f' (y - x)` over each of these faces is less than or equal to `ε * c * vol I`. We
  integrate a function of the norm `≤ ε * diam I.Icc` over a box of volume
  `∏ j ≠ i, (I.upper j - I.lower j)`. Since `diam I.Icc ≤ c * (I.upper i - I.lower i)`, we get the
  required estimate.  -/
  have Hl : I.lower i ∈ Icc (I.lower i) (I.upper i) := set.left_mem_Icc.2 (I.lower_le_upper i),
  have Hu : I.upper i ∈ Icc (I.lower i) (I.upper i) := set.right_mem_Icc.2 (I.lower_le_upper i),
  have Hi : ∀ x ∈ Icc (I.lower i) (I.upper i),
    integrable.{0 u u} (I.face i) ⊥ (f ∘ i.insert_nth x) box_additive_map.volume,
    from λ x hx, integrable_of_continuous_on _ (box.continuous_on_face_Icc hfc hx) volume,
  /- We start with an estimate: the difference of the values of `f` at the corresponding points
  of the faces `x i = I.lower i` and `x i = I.upper i` is `(2 * ε * diam I.Icc)`-close to the value
  of `f'` on `pi.single i (I.upper i - I.lower i) = lᵢ • eᵢ`, where `lᵢ = I.upper i - I.lower i`
  is the length of `i`-th edge of `I` and `eᵢ = pi.single i 1` is the `i`-th unit vector. -/
  have : ∀ y ∈ (I.face i).Icc, ∥f' (pi.single i (I.upper i - I.lower i)) -
    (f (i.insert_nth (I.upper i) y) - f (i.insert_nth (I.lower i) y))∥ ≤ 2 * ε * diam I.Icc,
  { intros y hy,
    set g := λ y, f y - a - f' (y - x) with hg,
    change ∀ y ∈ I.Icc, ∥g y∥ ≤ ε * ∥y - x∥ at hε,
    clear_value g, obtain rfl : f = λ y, a + f' (y - x) + g y, by simp [hg],
    convert_to ∥g (i.insert_nth (I.lower i) y) - g (i.insert_nth (I.upper i) y)∥ ≤ _,
    { congr' 1,
      have := fin.insert_nth_sub_same i (I.upper i) (I.lower i) y,
      simp only [← this, f'.map_sub], abel },
    { have : ∀ z ∈ Icc (I.lower i) (I.upper i), i.insert_nth z y ∈ I.Icc,
        from λ z hz, I.maps_to_insert_nth_face_Icc hz hy,
      replace hε : ∀ y ∈ I.Icc, ∥g y∥ ≤ ε * diam I.Icc,
      { intros y hy,
        refine (hε y hy).trans (mul_le_mul_of_nonneg_left _ h0.le),
        rw ← dist_eq_norm,
        exact dist_le_diam_of_mem I.is_compact_Icc.bounded hy hxI },
      rw [two_mul, add_mul],
      exact norm_sub_le_of_le (hε _ (this _ Hl)) (hε _ (this _ Hu)) } },
  calc ∥(∏ j, (I.upper j - I.lower j)) • f' (pi.single i 1) -
    (integral (I.face i) ⊥ (f ∘ i.insert_nth (I.upper i)) box_additive_map.volume -
      integral (I.face i) ⊥ (f ∘ i.insert_nth (I.lower i)) box_additive_map.volume)∥
      = ∥integral.{0 u u} (I.face i) ⊥
          (λ (x : fin n → ℝ), f' (pi.single i (I.upper i - I.lower i)) -
          (f (i.insert_nth (I.upper i) x) - f (i.insert_nth (I.lower i) x)))
          box_additive_map.volume∥ :
    begin
      rw [← integral_sub (Hi _ Hu) (Hi _ Hl), ← box.volume_face_mul i, mul_smul, ← box.volume_apply,
        ← box_additive_map.to_smul_apply, ← integral_const, ← box_additive_map.volume,
        ← integral_sub (integrable_const _) ((Hi _ Hu).sub (Hi _ Hl))],
      simp only [(∘), pi.sub_def, ← f'.map_smul, ← pi.single_smul', smul_eq_mul, mul_one]
    end
  ... ≤ (volume (I.face i : set ℝⁿ)).to_real * (2 * ε * c * (I.upper i - I.lower i)) :
    begin
      -- The hard part of the estimate was done above, here we just replace `diam I.Icc`
      -- with `c * (I.upper i - I.lower i)`
      refine norm_integral_le_of_le_const (λ y hy, (this y hy).trans _) volume,
      rw mul_assoc (2 * ε),
      exact mul_le_mul_of_nonneg_left (I.diam_Icc_le_of_distortion_le i hc)
        (mul_nonneg zero_le_two h0.le)
    end
  ... = 2 * ε * c * ∏ j, (I.upper j - I.lower j) :
    begin
      rw [← measure.to_box_additive_apply, box.volume_apply, ← I.volume_face_mul i],
      ac_refl
    end
end

/-- If `f : ℝⁿ⁺¹ → E` is differentiable on a closed rectangular box `I` with derivative `f'`, then
the partial derivative `λ x, f' x (pi.single i 1)` is Henstock-Kurzweil integrable with integral
equal to the difference of integrals of `f` over the faces `x i = I.upper i` and `x i = I.lower i`.

More precisely, we use a non-standard generalization of the Henstock-Kurzweil integral and
we allow `f` to be non-differentiable (but still continuous) at a countable set of points.

TODO: If `n > 0`, then the condition at `x ∈ s` can be replaced by a much weaker estimate but this
requires either better integrability theorems, or usage of a filter depending on the countable set
`s` (we need to ensure that none of the faces of a partition contain a point from `s`). -/
lemma has_integral_bot_pderiv (f : ℝⁿ⁺¹ → E) (f' : ℝⁿ⁺¹ → ℝⁿ⁺¹ →L[ℝ] E) (s : set ℝⁿ⁺¹)
  (hs : countable s) (Hs : ∀ x ∈ s, continuous_within_at f I.Icc x)
  (Hd : ∀ x ∈ I.Icc \ s, has_fderiv_within_at f (f' x) I.Icc x) (i : fin (n + 1)) :
  has_integral.{0 u u} I ⊥ (λ x, f' x (pi.single i 1)) box_additive_map.volume
    (integral.{0 u u} (I.face i) ⊥ (λ x, f (i.insert_nth (I.upper i) x)) box_additive_map.volume -
      integral.{0 u u} (I.face i) ⊥ (λ x, f (i.insert_nth (I.lower i) x))
        box_additive_map.volume) :=
begin
  /- Note that `f` is continuous on `I.Icc`, hence it is integrable on the faces of all boxes
  `J ≤ I`, thus the difference of integrals over `x i = J.upper i` and `x i = J.lower i` is a
  box-additive function of `J ≤ I`. -/
  have Hc : continuous_on f I.Icc,
  { intros x hx,
    by_cases hxs : x ∈ s,
    exacts [Hs x hxs, (Hd x ⟨hx, hxs⟩).continuous_within_at] },
  set fI : ℝ → box (fin n) → E := λ y J,
    integral.{0 u u} J ⊥ (λ x, f (i.insert_nth y x)) box_additive_map.volume,
  set fb : Icc (I.lower i) (I.upper i) → fin n →ᵇᵃ[↑(I.face i)] E :=
    λ x, (integrable_of_continuous_on ⊥ (box.continuous_on_face_Icc Hc x.2) volume).to_box_additive,
  set F : fin (n + 1) →ᵇᵃ[I] E := box_additive_map.upper_sub_lower I i fI fb (λ x hx J, rfl),
  /- Thus our statement follows from some local estimates. -/
  change has_integral I ⊥ (λ x, f' x (pi.single i 1)) _ (F I),
  refine has_integral_of_le_Henstock_of_forall_is_o bot_le _ _ _ s hs _ _,
  { /- We use the volume as an upper estimate. -/
    exact (volume : measure ℝⁿ⁺¹).to_box_additive.restrict _ le_top },
  { exact λ J, ennreal.to_real_nonneg },
  { intros c x hx ε ε0,
    /- Near `x ∈ s` we choose `δ` so that both vectors are small. `volume J • eᵢ` is small because
    `volume J ≤ (2 * δ) ^ (n + 1)` is small, and the difference of the integrals is small
    because each of the integrals is close to `volume (J.face i) • f x`.
    TODO: there should be a shorter and more readable way to formalize this simple proof. -/
    have : ∀ᶠ δ in 𝓝[Ioi 0] (0 : ℝ), δ ∈ Ioc (0 : ℝ) (1 / 2) ∧
      (∀ y₁ y₂ ∈ closed_ball x δ ∩ I.Icc, ∥f y₁ - f y₂∥ ≤ ε / 2) ∧
      ((2 * δ) ^ (n + 1) * ∥f' x (pi.single i 1)∥ ≤ ε / 2),
    { refine eventually.and _ (eventually.and _ _),
      { exact Ioc_mem_nhds_within_Ioi ⟨le_rfl, one_half_pos⟩ },
      { rcases ((nhds_within_has_basis nhds_basis_closed_ball _).tendsto_iff
          nhds_basis_closed_ball).1 (Hs x hx.2) _ (half_pos $ half_pos ε0) with ⟨δ₁, δ₁0, hδ₁⟩,
        filter_upwards [Ioc_mem_nhds_within_Ioi ⟨le_rfl, δ₁0⟩],
        rintro δ hδ y₁ y₂ hy₁ hy₂,
        have : closed_ball x δ ∩ I.Icc ⊆ closed_ball x δ₁ ∩ I.Icc,
          from inter_subset_inter_left _ (closed_ball_subset_closed_ball hδ.2),
        rw ← dist_eq_norm,
        calc dist (f y₁) (f y₂) ≤ dist (f y₁) (f x) + dist (f y₂) (f x) : dist_triangle_right _ _ _
        ... ≤ ε / 2 / 2 + ε / 2 / 2 : add_le_add (hδ₁ _ $ this hy₁) (hδ₁ _ $ this hy₂)
        ... = ε / 2 : add_halves _ },
      { have : continuous_within_at (λ δ, (2 * δ) ^ (n + 1) * ∥f' x (pi.single i 1)∥)
          (Ioi (0 : ℝ)) 0 := ((continuous_within_at_id.const_mul _).pow _).mul_const _,
        refine this.eventually (ge_mem_nhds _),
        simpa using half_pos ε0 } },
    rcases this.exists with ⟨δ, ⟨hδ0, hδ12⟩, hdfδ, hδ⟩,
    refine ⟨δ, hδ0, λ J hJI hJδ hxJ hJc, add_halves ε ▸ _⟩,
    have Hl : J.lower i ∈ Icc (J.lower i) (J.upper i) := set.left_mem_Icc.2 (J.lower_le_upper i),
    have Hu : J.upper i ∈ Icc (J.lower i) (J.upper i) := set.right_mem_Icc.2 (J.lower_le_upper i),
    have Hi : ∀ x ∈ Icc (J.lower i) (J.upper i),
      integrable.{0 u u} (J.face i) ⊥ (λ y, f (i.insert_nth x y)) box_additive_map.volume,
      from λ x hx, integrable_of_continuous_on _
        (box.continuous_on_face_Icc (Hc.mono $ box.le_iff_Icc.1 hJI) hx) volume,
    have hJδ' : J.Icc ⊆ closed_ball x δ ∩ I.Icc,
      from subset_inter hJδ (box.le_iff_Icc.1 hJI),
    have Hmaps : ∀ z ∈ Icc (J.lower i) (J.upper i),
      maps_to (i.insert_nth z) (J.face i).Icc (closed_ball x δ ∩ I.Icc),
      from λ z hz, (J.maps_to_insert_nth_face_Icc hz).mono subset.rfl hJδ',
    simp only [dist_eq_norm, F, fI], dsimp,
    rw [← integral_sub (Hi _ Hu) (Hi _ Hl)],
    refine (norm_sub_le _ _).trans (add_le_add _ _),
    { simp_rw [box_additive_map.volume_apply, norm_smul, real.norm_eq_abs, abs_prod],
      refine (mul_le_mul_of_nonneg_right _ $ norm_nonneg _).trans hδ,
      have : ∀ j, |J.upper j - J.lower j| ≤ 2 * δ,
      { intro j,
        calc dist (J.upper j) (J.lower j) ≤ dist J.upper J.lower : dist_le_pi_dist _ _ _
        ... ≤ dist J.upper x + dist J.lower x : dist_triangle_right _ _ _
        ... ≤ δ + δ : add_le_add (hJδ J.upper_mem_Icc) (hJδ J.lower_mem_Icc)
        ... = 2 * δ : (two_mul δ).symm },
      calc (∏ j, |J.upper j - J.lower j|) ≤ ∏ j : fin (n + 1), (2 * δ) :
        prod_le_prod (λ _ _ , abs_nonneg _) (λ j hj, this j)
      ... = (2 * δ) ^ (n + 1) : by simp },
    { refine (norm_integral_le_of_le_const (λ y hy,
        hdfδ _ _ (Hmaps _ Hu hy) (Hmaps _ Hl hy)) _).trans _,
      refine (mul_le_mul_of_nonneg_right _ (half_pos ε0).le).trans_eq (one_mul _),
      rw [box.coe_eq_pi, real.volume_pi_Ioc_to_real (box.lower_le_upper _)],
      refine prod_le_one (λ _ _, sub_nonneg.2 $ box.lower_le_upper _ _) (λ j hj, _),
      calc J.upper (i.succ_above j) - J.lower (i.succ_above j)
          ≤ dist (J.upper (i.succ_above j)) (J.lower (i.succ_above j)) : le_abs_self _
      ... ≤ dist J.upper J.lower : dist_le_pi_dist J.upper J.lower (i.succ_above j)
      ... ≤ dist J.upper x + dist J.lower x : dist_triangle_right _ _ _
      ... ≤ δ + δ : add_le_add (hJδ J.upper_mem_Icc) (hJδ J.lower_mem_Icc)
      ... ≤ 1 / 2 + 1 / 2 : add_le_add hδ12 hδ12
      ... = 1 : add_halves 1 } },
  { intros c x hx ε ε0,
    /- At a point `x ∉ s`, we unfold the definition of Fréchet differentiability, then use
    an estimate we proved earlier in this file. -/
    rcases exists_pos_mul_lt ε0 (2 * c) with ⟨ε', ε'0, hlt⟩,
    rcases (nhds_within_has_basis nhds_basis_closed_ball _).mem_iff.1 ((Hd x hx).def ε'0)
      with ⟨δ, δ0, Hδ⟩,
    refine ⟨δ, δ0, λ J hle hJδ hxJ hJc, _⟩,
    simp only [box_additive_map.volume_apply, box.volume_apply, dist_eq_norm],
    refine (norm_volume_sub_integral_face_upper_sub_lower_smul_le _
      (Hc.mono $ box.le_iff_Icc.1 hle) hxJ ε'0 (λ y hy, Hδ _) (hJc rfl)).trans _,
    { exact ⟨hJδ hy, box.le_iff_Icc.1 hle hy⟩ },
    { rw [mul_right_comm (2 : ℝ), ← box.volume_apply],
      exact mul_le_mul_of_nonneg_right hlt.le ennreal.to_real_nonneg } }
end

/-- Divergence theorem for a Henstock-Kurzweil style integral.

If `f : ℝⁿ⁺¹ → Eⁿ⁺¹` is differentiable on a closed rectangular box `I` with derivative `f'`, then
the divergence `∑ i, f' x (pi.single i 1) i` is Henstock-Kurzweil integrable with integral equal to
the sum of integrals of `f` over the faces of `I` taken with appropriate signs.

More precisely, we use a non-standard generalization of the Henstock-Kurzweil integral and
we allow `f` to be non-differentiable (but still continuous) at a countable set of points. -/
lemma has_integral_bot_divergence_of_forall_has_deriv_within_at
  (f : ℝⁿ⁺¹ → Eⁿ⁺¹) (f' : ℝⁿ⁺¹ → ℝⁿ⁺¹ →L[ℝ] Eⁿ⁺¹) (s : set ℝⁿ⁺¹) (hs : countable s)
  (Hs : ∀ x ∈ s, continuous_within_at f I.Icc x)
  (Hd : ∀ x ∈ I.Icc \ s, has_fderiv_within_at f (f' x) I.Icc x) :
  has_integral.{0 u u} I ⊥ (λ x, ∑ i, f' x (pi.single i 1) i)
    box_additive_map.volume
    (∑ i, (integral.{0 u u} (I.face i) ⊥ (λ x, f (i.insert_nth (I.upper i) x) i)
      box_additive_map.volume -
      integral.{0 u u} (I.face i) ⊥ (λ x, f (i.insert_nth (I.lower i) x) i)
        box_additive_map.volume)) :=
begin
  refine has_integral_sum (λ i hi, _), clear hi,
  simp only [has_fderiv_within_at_pi', continuous_within_at_pi] at Hd Hs,
  convert has_integral_bot_pderiv I _ _ s hs (λ x hx, Hs x hx i) (λ x hx, Hd x hx i) i
end

end box_integral


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Patrick Massot
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.uniform_space.basic
import Mathlib.tactic.apply_fun
import Mathlib.PostPort

universes u u_1 v w

namespace Mathlib

/-!
# Hausdorff properties of uniform spaces. Separation quotient.

This file studies uniform spaces whose underlying topological spaces are separated
(also known as Hausdorff or T₂).
This turns out to be equivalent to asking that the intersection of all entourages
is the diagonal only. This condition actually implies the stronger separation property
that the space is regular (T₃), hence those conditions are equivalent for topologies coming from
a uniform structure.

More generally, the intersection `𝓢 X` of all entourages of `X`, which has type `set (X × X)` is an
equivalence relation on `X`. Points which are equivalent under the relation are basically
undistinguishable from the point of view of the uniform structure. For instance any uniformly
continuous function will send equivalent points to the same value.

The quotient `separation_quotient X` of `X` by `𝓢 X` has a natural uniform structure which is
separated, and satisfies a universal property: every uniformly continuous function
from `X` to a separated uniform space uniquely factors through `separation_quotient X`.
As usual, this allows to turn `separation_quotient` into a functor (but we don't use the
category theory library in this file).

These notions admit relative versions, one can ask that `s : set X` is separated, this
is equivalent to asking that the uniform structure induced on `s` is separated.

## Main definitions

* `separation_relation X : set (X × X)`: the separation relation
* `separated_space X`: a predicate class asserting that `X` is separated
* `is_separated s`: a predicate asserting that `s : set X` is separated
* `separation_quotient X`: the maximal separated quotient of `X`.
* `separation_quotient.lift f`: factors a map `f : X → Y` through the separation quotient of `X`.
* `separation_quotient.map f`: turns a map `f : X → Y` into a map between the separation quotients
  of `X` and `Y`.

## Main results

* `separated_iff_t2`: the equivalence between being separated and being Hausdorff for uniform
  spaces.
* `separation_quotient.uniform_continuous_lift`: factoring a uniformly continuous map through the
  separation quotient gives a uniformly continuous map.
* `separation_quotient.uniform_continuous_map`: maps induced between separation quotients are
  uniformly continuous.

## Notations

Localized in `uniformity`, we have the notation `𝓢 X` for the separation relation
on a uniform space `X`,

## Implementation notes

The separation setoid `separation_setoid` is not declared as a global instance.
It is made a local instance while building the theory of `separation_quotient`.
The factored map `separation_quotient.lift f` is defined without imposing any condition on
`f`, but returns junk if `f` is not uniformly continuous (constant junk hence it is always
uniformly continuous).

-/

/-!
### Separated uniform spaces
-/

/-- The separation relation is the intersection of all entourages.
  Two points which are related by the separation relation are "indistinguishable"
  according to the uniform structure. -/
protected def separation_rel (α : Type u) [u : uniform_space α] : set (α × α) :=
  ⋂₀filter.sets (uniformity α)

theorem separated_equiv {α : Type u} [uniform_space α] :
    equivalence fun (x y : α) => (x, y) ∈ Mathlib.separation_rel α :=
  sorry

/-- A uniform space is separated if its separation relation is trivial (each point
is related only to itself). -/
def separated_space (α : Type u) [uniform_space α] := Mathlib.separation_rel α = id_rel

theorem separated_def {α : Type u} [uniform_space α] :
    separated_space α ↔ ∀ (x y : α), (∀ (r : set (α × α)), r ∈ uniformity α → (x, y) ∈ r) → x = y :=
  sorry

theorem separated_def' {α : Type u} [uniform_space α] :
    separated_space α ↔
        ∀ (x y : α), x ≠ y → ∃ (r : set (α × α)), ∃ (H : r ∈ uniformity α), ¬(x, y) ∈ r :=
  sorry

theorem id_rel_sub_separation_relation (α : Type u_1) [uniform_space α] :
    id_rel ⊆ Mathlib.separation_rel α :=
  sorry

theorem separation_rel_comap {α : Type u} {β : Type v} [uniform_space α] [uniform_space β]
    {f : α → β} (h : _inst_1 = uniform_space.comap f _inst_2) :
    Mathlib.separation_rel α = prod.map f f ⁻¹' Mathlib.separation_rel β :=
  sorry

protected theorem filter.has_basis.separation_rel {α : Type u} [uniform_space α] {ι : Type u_1}
    {p : ι → Prop} {s : ι → set (α × α)} (h : filter.has_basis (uniformity α) p s) :
    Mathlib.separation_rel α = set.Inter fun (i : ι) => set.Inter fun (H : i ∈ set_of p) => s i :=
  sorry

theorem separation_rel_eq_inter_closure {α : Type u} [uniform_space α] :
    Mathlib.separation_rel α = ⋂₀(closure '' filter.sets (uniformity α)) :=
  sorry

theorem is_closed_separation_rel {α : Type u} [uniform_space α] :
    is_closed (Mathlib.separation_rel α) :=
  sorry

theorem separated_iff_t2 {α : Type u} [uniform_space α] : separated_space α ↔ t2_space α := sorry

protected instance separated_regular {α : Type u} [uniform_space α] [separated_space α] :
    regular_space α :=
  regular_space.mk
    fun (s : set α) (a : α) (hs : is_closed s) (ha : ¬a ∈ s) =>
      (fun (this : sᶜ ∈ nhds a) =>
          (fun
              (this :
              (set_of fun (p : α × α) => prod.fst p = a → prod.snd p ∈ (sᶜ)) ∈ uniformity α) =>
              sorry)
            (iff.mp mem_nhds_uniformity_iff_right this))
        (mem_nhds_sets hs ha)

/-!
### Separated sets
-/

/-- A set `s` in a uniform space `α` is separated if the separation relation `𝓢 α`
induces the trivial relation on `s`. -/
def is_separated {α : Type u} [uniform_space α] (s : set α) :=
  ∀ (x y : α), x ∈ s → y ∈ s → (x, y) ∈ Mathlib.separation_rel α → x = y

theorem is_separated_def {α : Type u} [uniform_space α] (s : set α) :
    is_separated s ↔ ∀ (x y : α), x ∈ s → y ∈ s → (x, y) ∈ Mathlib.separation_rel α → x = y :=
  iff.rfl

theorem is_separated_def' {α : Type u} [uniform_space α] (s : set α) :
    is_separated s ↔ set.prod s s ∩ Mathlib.separation_rel α ⊆ id_rel :=
  sorry

theorem univ_separated_iff {α : Type u} [uniform_space α] :
    is_separated set.univ ↔ separated_space α :=
  sorry

theorem is_separated_of_separated_space {α : Type u} [uniform_space α] [separated_space α]
    (s : set α) : is_separated s :=
  sorry

theorem is_separated_iff_induced {α : Type u} [uniform_space α] {s : set α} :
    is_separated s ↔ separated_space ↥s :=
  sorry

theorem eq_of_uniformity_inf_nhds_of_is_separated {α : Type u} [uniform_space α] {s : set α}
    (hs : is_separated s) {x : α} {y : α} :
    x ∈ s → y ∈ s → cluster_pt (x, y) (uniformity α) → x = y :=
  sorry

theorem eq_of_uniformity_inf_nhds {α : Type u} [uniform_space α] [separated_space α] {x : α}
    {y : α} : cluster_pt (x, y) (uniformity α) → x = y :=
  sorry

/-!
### Separation quotient
-/

namespace uniform_space

/-- The separation relation of a uniform space seen as a setoid. -/
def separation_setoid (α : Type u) [uniform_space α] : setoid α :=
  setoid.mk (fun (x y : α) => (x, y) ∈ Mathlib.separation_rel α) separated_equiv

protected instance separation_setoid.uniform_space {α : Type u} [u : uniform_space α] :
    uniform_space (quotient (separation_setoid α)) :=
  mk
    (core.mk
      (filter.map (fun (p : α × α) => (quotient.mk (prod.fst p), quotient.mk (prod.snd p)))
        (core.uniformity to_core))
      sorry sorry sorry)
    sorry

theorem uniformity_quotient {α : Type u} [uniform_space α] :
    uniformity (quotient (separation_setoid α)) =
        filter.map (fun (p : α × α) => (quotient.mk (prod.fst p), quotient.mk (prod.snd p)))
          (uniformity α) :=
  rfl

theorem uniform_continuous_quotient_mk {α : Type u} [uniform_space α] :
    uniform_continuous quotient.mk :=
  le_refl
    (filter.map (fun (x : α × α) => (quotient.mk (prod.fst x), quotient.mk (prod.snd x)))
      (uniformity α))

theorem uniform_continuous_quotient {α : Type u} {β : Type v} [uniform_space α] [uniform_space β]
    {f : quotient (separation_setoid α) → β}
    (hf : uniform_continuous fun (x : α) => f (quotient.mk x)) : uniform_continuous f :=
  hf

theorem uniform_continuous_quotient_lift {α : Type u} {β : Type v} [uniform_space α]
    [uniform_space β] {f : α → β} {h : ∀ (a b : α), (a, b) ∈ Mathlib.separation_rel α → f a = f b}
    (hf : uniform_continuous f) :
    uniform_continuous fun (a : quotient (separation_setoid α)) => quotient.lift f h a :=
  uniform_continuous_quotient hf

theorem uniform_continuous_quotient_lift₂ {α : Type u} {β : Type v} {γ : Type w} [uniform_space α]
    [uniform_space β] [uniform_space γ] {f : α → β → γ}
    {h :
      ∀ (a : α) (c : β) (b : α) (d : β),
        (a, b) ∈ Mathlib.separation_rel α → (c, d) ∈ Mathlib.separation_rel β → f a c = f b d}
    (hf : uniform_continuous fun (p : α × β) => f (prod.fst p) (prod.snd p)) :
    uniform_continuous
        fun (p : quotient (separation_setoid α) × quotient (separation_setoid β)) =>
          quotient.lift₂ f h (prod.fst p) (prod.snd p) :=
  sorry

theorem comap_quotient_le_uniformity {α : Type u} [uniform_space α] :
    filter.comap (fun (p : α × α) => (quotient.mk (prod.fst p), quotient.mk (prod.snd p)))
          (uniformity (quotient (separation_setoid α))) ≤
        uniformity α :=
  sorry

theorem comap_quotient_eq_uniformity {α : Type u} [uniform_space α] :
    filter.comap (fun (p : α × α) => (quotient.mk (prod.fst p), quotient.mk (prod.snd p)))
          (uniformity (quotient (separation_setoid α))) =
        uniformity α :=
  le_antisymm comap_quotient_le_uniformity filter.le_comap_map

protected instance separated_separation {α : Type u} [uniform_space α] :
    separated_space (quotient (separation_setoid α)) :=
  set.ext fun (_x : quotient (separation_setoid α) × quotient (separation_setoid α)) => sorry

theorem separated_of_uniform_continuous {α : Type u} {β : Type v} [uniform_space α]
    [uniform_space β] {f : α → β} {x : α} {y : α} (H : uniform_continuous f) (h : x ≈ y) :
    f x ≈ f y :=
  fun (_x : set (β × β)) (h' : _x ∈ filter.sets (uniformity β)) =>
    h ((fun (x : α × α) => (f (prod.fst x), f (prod.snd x))) ⁻¹' _x) (H h')

theorem eq_of_separated_of_uniform_continuous {α : Type u} {β : Type v} [uniform_space α]
    [uniform_space β] [separated_space β] {f : α → β} {x : α} {y : α} (H : uniform_continuous f)
    (h : x ≈ y) : f x = f y :=
  iff.mp separated_def _inst_4 (f x) (f y) (separated_of_uniform_continuous H h)

/-- The maximal separated quotient of a uniform space `α`. -/
def separation_quotient (α : Type u_1) [uniform_space α] := quotient (separation_setoid α)

namespace separation_quotient

protected instance uniform_space {α : Type u} [uniform_space α] :
    uniform_space (separation_quotient α) :=
  id separation_setoid.uniform_space

protected instance separated_space {α : Type u} [uniform_space α] :
    separated_space (separation_quotient α) :=
  id uniform_space.separated_separation

protected instance inhabited {α : Type u} [uniform_space α] [Inhabited α] :
    Inhabited (separation_quotient α) :=
  eq.mpr sorry quotient.inhabited

/-- Factoring functions to a separated space through the separation quotient. -/
def lift {α : Type u} {β : Type v} [uniform_space α] [uniform_space β] [separated_space β]
    (f : α → β) : separation_quotient α → β :=
  dite (uniform_continuous f) (fun (h : uniform_continuous f) => quotient.lift f sorry)
    fun (h : ¬uniform_continuous f) (x : separation_quotient α) => f Inhabited.default

theorem lift_mk {α : Type u} {β : Type v} [uniform_space α] [uniform_space β] [separated_space β]
    {f : α → β} (h : uniform_continuous f) (a : α) : lift f (quotient.mk a) = f a :=
  sorry

theorem uniform_continuous_lift {α : Type u} {β : Type v} [uniform_space α] [uniform_space β]
    [separated_space β] (f : α → β) : uniform_continuous (lift f) :=
  sorry

/-- The separation quotient functor acting on functions. -/
def map {α : Type u} {β : Type v} [uniform_space α] [uniform_space β] (f : α → β) :
    separation_quotient α → separation_quotient β :=
  lift (quotient.mk ∘ f)

theorem map_mk {α : Type u} {β : Type v} [uniform_space α] [uniform_space β] {f : α → β}
    (h : uniform_continuous f) (a : α) : map f (quotient.mk a) = quotient.mk (f a) :=
  sorry

theorem uniform_continuous_map {α : Type u} {β : Type v} [uniform_space α] [uniform_space β]
    (f : α → β) : uniform_continuous (map f) :=
  uniform_continuous_lift (quotient.mk ∘ f)

theorem map_unique {α : Type u} {β : Type v} [uniform_space α] [uniform_space β] {f : α → β}
    (hf : uniform_continuous f) {g : separation_quotient α → separation_quotient β}
    (comm : quotient.mk ∘ f = g ∘ quotient.mk) : map f = g :=
  funext
    fun (x : separation_quotient α) =>
      quot.induction_on x fun (a : α) => Eq.trans (map_mk hf a) (congr_fun comm a)

theorem map_id {α : Type u} [uniform_space α] : map id = id := map_unique uniform_continuous_id rfl

theorem map_comp {α : Type u} {β : Type v} {γ : Type w} [uniform_space α] [uniform_space β]
    [uniform_space γ] {f : α → β} {g : β → γ} (hf : uniform_continuous f)
    (hg : uniform_continuous g) : map g ∘ map f = map (g ∘ f) :=
  sorry

end separation_quotient

theorem separation_prod {α : Type u} {β : Type v} [uniform_space α] [uniform_space β] {a₁ : α}
    {a₂ : α} {b₁ : β} {b₂ : β} : (a₁, b₁) ≈ (a₂, b₂) ↔ a₁ ≈ a₂ ∧ b₁ ≈ b₂ :=
  sorry

protected instance separated.prod {α : Type u} {β : Type v} [uniform_space α] [uniform_space β]
    [separated_space α] [separated_space β] : separated_space (α × β) :=
  iff.mpr separated_def
    fun (x y : α × β) (H : ∀ (r : set ((α × β) × α × β)), r ∈ uniformity (α × β) → (x, y) ∈ r) =>
      prod.ext (eq_of_separated_of_uniform_continuous uniform_continuous_fst H)
        (eq_of_separated_of_uniform_continuous uniform_continuous_snd H)

end Mathlib


import game.max.level09 -- hide

open_locale classical -- hide

noncomputable theory -- hide

namespace xena -- hide

/-
# Chapter 4 : Max

## Level 10

And finally `lt_max_iff`.
-/

/- Lemma
If $a$, $b$, $c$ are real numbers,
then $a<\max(b,c)$ iff ($a<b$ or $a<c$).
-/

theorem lt_max_iff {a b c : ℝ} : a < max b c ↔ a < b ∨ a < c :=
begin
  split,
  intro h,
  cases le_total b c with hbc hcb,
  rw max_eq_right at h,
  right,
  exact h,
  exact hbc,
  rw max_eq_left at h,
  left,
  exact h,
  exact hcb,

  intro j,
  cases j with d hd,
  apply lt_of_lt_of_le d,
  apply le_max_left,
  apply lt_of_lt_of_le hd,
  apply le_max_right,
end

end xena --hide