/-
Copyright (c) 2020 Joseph Myers. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Joseph Myers.
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.analysis.normed_space.inner_product
import Mathlib.algebra.quadratic_discriminant
import Mathlib.analysis.normed_space.add_torsor
import Mathlib.data.matrix.notation
import Mathlib.linear_algebra.affine_space.finite_dimensional
import Mathlib.tactic.fin_cases
import Mathlib.PostPort

universes u_1 u_2 u_3 u_4

namespace Mathlib

/-!
# Euclidean spaces

This file makes some definitions and proves very basic geometrical
results about real inner product spaces and Euclidean affine spaces.
Results about real inner product spaces that involve the norm and
inner product but not angles generally go in
`analysis.normed_space.inner_product`.  Results with longer
proofs or more geometrical content generally go in separate files.

## Main definitions

* `inner_product_geometry.angle` is the undirected angle between two
  vectors.

* `euclidean_geometry.angle`, with notation `∠`, is the undirected
  angle determined by three points.

* `euclidean_geometry.orthogonal_projection` is the orthogonal
  projection of a point onto an affine subspace.

* `euclidean_geometry.reflection` is the reflection of a point in an
  affine subspace.

## Implementation notes

To declare `P` as the type of points in a Euclidean affine space with
`V` as the type of vectors, use `[inner_product_space ℝ V] [metric_space P]
[normed_add_torsor V P]`.  This works better with `out_param` to make
`V` implicit in most cases than having a separate type alias for
Euclidean affine spaces.

Rather than requiring Euclidean affine spaces to be finite-dimensional
(as in the definition on Wikipedia), this is specified only for those
theorems that need it.

## References

* https://en.wikipedia.org/wiki/Euclidean_space

-/

namespace inner_product_geometry

/-!
### Geometrical results on real inner product spaces

This section develops some geometrical definitions and results on real
inner product spaces, where those definitions and results can most
conveniently be developed in terms of vectors and then used to deduce
corresponding results for Euclidean affine spaces.
-/

/-- The undirected angle between two vectors. If either vector is 0,
this is π/2. -/
def angle {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V) : ℝ :=
  real.arccos (inner x y / (norm x * norm y))

/-- The cosine of the angle between two vectors. -/
theorem cos_angle {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V) :
    real.cos (angle x y) = inner x y / (norm x * norm y) :=
  real.cos_arccos (and.left (iff.mp abs_le (abs_real_inner_div_norm_mul_norm_le_one x y)))
    (and.right (iff.mp abs_le (abs_real_inner_div_norm_mul_norm_le_one x y)))

/-- The angle between two vectors does not depend on their order. -/
theorem angle_comm {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V) :
    angle x y = angle y x :=
  sorry

/-- The angle between the negation of two vectors. -/
@[simp] theorem angle_neg_neg {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V) :
    angle (-x) (-y) = angle x y :=
  sorry

/-- The angle between two vectors is nonnegative. -/
theorem angle_nonneg {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V) : 0 ≤ angle x y :=
  real.arccos_nonneg (inner x y / (norm x * norm y))

/-- The angle between two vectors is at most π. -/
theorem angle_le_pi {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V) :
    angle x y ≤ real.pi :=
  real.arccos_le_pi (inner x y / (norm x * norm y))

/-- The angle between a vector and the negation of another vector. -/
theorem angle_neg_right {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V) :
    angle x (-y) = real.pi - angle x y :=
  sorry

/-- The angle between the negation of a vector and another vector. -/
theorem angle_neg_left {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V) :
    angle (-x) y = real.pi - angle x y :=
  sorry

/-- The angle between the zero vector and a vector. -/
@[simp] theorem angle_zero_left {V : Type u_1} [inner_product_space ℝ V] (x : V) :
    angle 0 x = real.pi / bit0 1 :=
  sorry

/-- The angle between a vector and the zero vector. -/
@[simp] theorem angle_zero_right {V : Type u_1} [inner_product_space ℝ V] (x : V) :
    angle x 0 = real.pi / bit0 1 :=
  sorry

/-- The angle between a nonzero vector and itself. -/
@[simp] theorem angle_self {V : Type u_1} [inner_product_space ℝ V] {x : V} (hx : x ≠ 0) :
    angle x x = 0 :=
  sorry

/-- The angle between a nonzero vector and its negation. -/
@[simp] theorem angle_self_neg_of_nonzero {V : Type u_1} [inner_product_space ℝ V] {x : V}
    (hx : x ≠ 0) : angle x (-x) = real.pi :=
  eq.mpr (id (Eq._oldrec (Eq.refl (angle x (-x) = real.pi)) (angle_neg_right x x)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (real.pi - angle x x = real.pi)) (angle_self hx)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (real.pi - 0 = real.pi)) (sub_zero real.pi)))
        (Eq.refl real.pi)))

/-- The angle between the negation of a nonzero vector and that
vector. -/
@[simp] theorem angle_neg_self_of_nonzero {V : Type u_1} [inner_product_space ℝ V] {x : V}
    (hx : x ≠ 0) : angle (-x) x = real.pi :=
  eq.mpr (id (Eq._oldrec (Eq.refl (angle (-x) x = real.pi)) (angle_comm (-x) x)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (angle x (-x) = real.pi)) (angle_self_neg_of_nonzero hx)))
      (Eq.refl real.pi))

/-- The angle between a vector and a positive multiple of a vector. -/
@[simp] theorem angle_smul_right_of_pos {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V)
    {r : ℝ} (hr : 0 < r) : angle x (r • y) = angle x y :=
  sorry

/-- The angle between a positive multiple of a vector and a vector. -/
@[simp] theorem angle_smul_left_of_pos {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V)
    {r : ℝ} (hr : 0 < r) : angle (r • x) y = angle x y :=
  eq.mpr (id (Eq._oldrec (Eq.refl (angle (r • x) y = angle x y)) (angle_comm (r • x) y)))
    (eq.mpr
      (id (Eq._oldrec (Eq.refl (angle y (r • x) = angle x y)) (angle_smul_right_of_pos y x hr)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (angle y x = angle x y)) (angle_comm y x)))
        (Eq.refl (angle x y))))

/-- The angle between a vector and a negative multiple of a vector. -/
@[simp] theorem angle_smul_right_of_neg {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V)
    {r : ℝ} (hr : r < 0) : angle x (r • y) = angle x (-y) :=
  sorry

/-- The angle between a negative multiple of a vector and a vector. -/
@[simp] theorem angle_smul_left_of_neg {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V)
    {r : ℝ} (hr : r < 0) : angle (r • x) y = angle (-x) y :=
  eq.mpr (id (Eq._oldrec (Eq.refl (angle (r • x) y = angle (-x) y)) (angle_comm (r • x) y)))
    (eq.mpr
      (id (Eq._oldrec (Eq.refl (angle y (r • x) = angle (-x) y)) (angle_smul_right_of_neg y x hr)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (angle y (-x) = angle (-x) y)) (angle_comm y (-x))))
        (Eq.refl (angle (-x) y))))

/-- The cosine of the angle between two vectors, multiplied by the
product of their norms. -/
theorem cos_angle_mul_norm_mul_norm {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V) :
    real.cos (angle x y) * (norm x * norm y) = inner x y :=
  sorry

/-- The sine of the angle between two vectors, multiplied by the
product of their norms. -/
theorem sin_angle_mul_norm_mul_norm {V : Type u_1} [inner_product_space ℝ V] (x : V) (y : V) :
    real.sin (angle x y) * (norm x * norm y) =
        real.sqrt (inner x x * inner y y - inner x y * inner x y) :=
  sorry

/-- The angle between two vectors is zero if and only if they are
nonzero and one is a positive multiple of the other. -/
theorem angle_eq_zero_iff {V : Type u_1} [inner_product_space ℝ V] {x : V} {y : V} :
    angle x y = 0 ↔ x ≠ 0 ∧ ∃ (r : ℝ), 0 < r ∧ y = r • x :=
  sorry

/-- The angle between two vectors is π if and only if they are nonzero
and one is a negative multiple of the other. -/
theorem angle_eq_pi_iff {V : Type u_1} [inner_product_space ℝ V] {x : V} {y : V} :
    angle x y = real.pi ↔ x ≠ 0 ∧ ∃ (r : ℝ), r < 0 ∧ y = r • x :=
  sorry

/-- If the angle between two vectors is π, the angles between those
vectors and a third vector add to π. -/
theorem angle_add_angle_eq_pi_of_angle_eq_pi {V : Type u_1} [inner_product_space ℝ V] {x : V}
    {y : V} (z : V) (h : angle x y = real.pi) : angle x z + angle y z = real.pi :=
  sorry

/-- Two vectors have inner product 0 if and only if the angle between
them is π/2. -/
theorem inner_eq_zero_iff_angle_eq_pi_div_two {V : Type u_1} [inner_product_space ℝ V] (x : V)
    (y : V) : inner x y = 0 ↔ angle x y = real.pi / bit0 1 :=
  sorry

end inner_product_geometry

namespace euclidean_geometry

/-!
### Geometrical results on Euclidean affine spaces

This section develops some geometrical definitions and results on
Euclidean affine spaces.
-/

/-- The undirected angle at `p2` between the line segments to `p1` and
`p3`. If either of those points equals `p2`, this is π/2. Use
`open_locale euclidean_geometry` to access the `∠ p1 p2 p3`
notation. -/
def angle {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] (p1 : P) (p2 : P) (p3 : P) : ℝ :=
  inner_product_geometry.angle (p1 -ᵥ p2) (p3 -ᵥ p2)

/-- The angle at a point does not depend on the order of the other two
points. -/
theorem angle_comm {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] (p1 : P) (p2 : P) (p3 : P) : angle p1 p2 p3 = angle p3 p2 p1 :=
  inner_product_geometry.angle_comm (p1 -ᵥ p2) (p3 -ᵥ p2)

/-- The angle at a point is nonnegative. -/
theorem angle_nonneg {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] (p1 : P) (p2 : P) (p3 : P) : 0 ≤ angle p1 p2 p3 :=
  inner_product_geometry.angle_nonneg (p1 -ᵥ p2) (p3 -ᵥ p2)

/-- The angle at a point is at most π. -/
theorem angle_le_pi {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] (p1 : P) (p2 : P) (p3 : P) : angle p1 p2 p3 ≤ real.pi :=
  inner_product_geometry.angle_le_pi (p1 -ᵥ p2) (p3 -ᵥ p2)

/-- The angle ∠AAB at a point. -/
theorem angle_eq_left {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] (p1 : P) (p2 : P) : angle p1 p1 p2 = real.pi / bit0 1 :=
  sorry

/-- The angle ∠ABB at a point. -/
theorem angle_eq_right {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] (p1 : P) (p2 : P) : angle p1 p2 p2 = real.pi / bit0 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (angle p1 p2 p2 = real.pi / bit0 1)) (angle_comm p1 p2 p2)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (angle p2 p2 p1 = real.pi / bit0 1)) (angle_eq_left p2 p1)))
      (Eq.refl (real.pi / bit0 1)))

/-- The angle ∠ABA at a point. -/
theorem angle_eq_of_ne {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] {p1 : P} {p2 : P} (h : p1 ≠ p2) : angle p1 p2 p1 = 0 :=
  inner_product_geometry.angle_self fun (he : p1 -ᵥ p2 = 0) => h (iff.mp vsub_eq_zero_iff_eq he)

/-- If the angle ∠ABC at a point is π, the angle ∠BAC is 0. -/
theorem angle_eq_zero_of_angle_eq_pi_left {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {p1 : P} {p2 : P} {p3 : P}
    (h : angle p1 p2 p3 = real.pi) : angle p2 p1 p3 = 0 :=
  sorry

/-- If the angle ∠ABC at a point is π, the angle ∠BCA is 0. -/
theorem angle_eq_zero_of_angle_eq_pi_right {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {p1 : P} {p2 : P} {p3 : P}
    (h : angle p1 p2 p3 = real.pi) : angle p2 p3 p1 = 0 :=
  angle_eq_zero_of_angle_eq_pi_left
    (eq.mp (Eq._oldrec (Eq.refl (angle p1 p2 p3 = real.pi)) (angle_comm p1 p2 p3)) h)

/-- If ∠BCD = π, then ∠ABC = ∠ABD. -/
theorem angle_eq_angle_of_angle_eq_pi {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] (p1 : P) {p2 : P} {p3 : P} {p4 : P}
    (h : angle p2 p3 p4 = real.pi) : angle p1 p2 p3 = angle p1 p2 p4 :=
  sorry

/-- If ∠BCD = π, then ∠ACB + ∠ACD = π. -/
theorem angle_add_angle_eq_pi_of_angle_eq_pi {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] (p1 : P) {p2 : P} {p3 : P} {p4 : P}
    (h : angle p2 p3 p4 = real.pi) : angle p1 p3 p2 + angle p1 p3 p4 = real.pi :=
  sorry

/-- The inner product of two vectors given with `weighted_vsub`, in
terms of the pairwise distances. -/
theorem inner_weighted_vsub {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] {ι₁ : Type u_3} {s₁ : finset ι₁} {w₁ : ι₁ → ℝ} (p₁ : ι₁ → P)
    (h₁ : (finset.sum s₁ fun (i : ι₁) => w₁ i) = 0) {ι₂ : Type u_4} {s₂ : finset ι₂} {w₂ : ι₂ → ℝ}
    (p₂ : ι₂ → P) (h₂ : (finset.sum s₂ fun (i : ι₂) => w₂ i) = 0) :
    inner (coe_fn (finset.weighted_vsub s₁ p₁) w₁) (coe_fn (finset.weighted_vsub s₂ p₂) w₂) =
        (-finset.sum s₁
              fun (i₁ : ι₁) =>
                finset.sum s₂
                  fun (i₂ : ι₂) => w₁ i₁ * w₂ i₂ * (dist (p₁ i₁) (p₂ i₂) * dist (p₁ i₁) (p₂ i₂))) /
          bit0 1 :=
  sorry

/-- The distance between two points given with `affine_combination`,
in terms of the pairwise distances between the points in that
combination. -/
theorem dist_affine_combination {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {ι : Type u_3} {s : finset ι} {w₁ : ι → ℝ} {w₂ : ι → ℝ}
    (p : ι → P) (h₁ : (finset.sum s fun (i : ι) => w₁ i) = 1)
    (h₂ : (finset.sum s fun (i : ι) => w₂ i) = 1) :
    dist (coe_fn (finset.affine_combination s p) w₁) (coe_fn (finset.affine_combination s p) w₂) *
          dist (coe_fn (finset.affine_combination s p) w₁)
            (coe_fn (finset.affine_combination s p) w₂) =
        (-finset.sum s
              fun (i₁ : ι) =>
                finset.sum s
                  fun (i₂ : ι) =>
                    Sub.sub w₁ w₂ i₁ * Sub.sub w₁ w₂ i₂ *
                      (dist (p i₁) (p i₂) * dist (p i₁) (p i₂))) /
          bit0 1 :=
  sorry

/-- Suppose that `c₁` is equidistant from `p₁` and `p₂`, and the same
applies to `c₂`.  Then the vector between `c₁` and `c₂` is orthogonal
to that between `p₁` and `p₂`.  (In two dimensions, this says that the
diagonals of a kite are orthogonal.) -/
theorem inner_vsub_vsub_of_dist_eq_of_dist_eq {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {c₁ : P} {c₂ : P} {p₁ : P}
    {p₂ : P} (hc₁ : dist p₁ c₁ = dist p₂ c₁) (hc₂ : dist p₁ c₂ = dist p₂ c₂) :
    inner (c₂ -ᵥ c₁) (p₂ -ᵥ p₁) = 0 :=
  sorry

/-- The squared distance between points on a line (expressed as a
multiple of a fixed vector added to a point) and another point,
expressed as a quadratic. -/
theorem dist_smul_vadd_square {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] (r : ℝ) (v : V) (p₁ : P) (p₂ : P) :
    dist (r • v +ᵥ p₁) p₂ * dist (r • v +ᵥ p₁) p₂ =
        inner v v * r * r + bit0 1 * inner v (p₁ -ᵥ p₂) * r + inner (p₁ -ᵥ p₂) (p₁ -ᵥ p₂) :=
  sorry

/-- The condition for two points on a line to be equidistant from
another point. -/
theorem dist_smul_vadd_eq_dist {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {v : V} (p₁ : P) (p₂ : P) (hv : v ≠ 0) (r : ℝ) :
    dist (r • v +ᵥ p₁) p₂ = dist p₁ p₂ ↔ r = 0 ∨ r = -bit0 1 * inner v (p₁ -ᵥ p₂) / inner v v :=
  sorry

/-- Distances `r₁` `r₂` of `p` from two different points `c₁` `c₂` determine at
most two points `p₁` `p₂` in a two-dimensional subspace containing those points
(two circles intersect in at most two points). -/
theorem eq_of_dist_eq_of_dist_eq_of_mem_of_findim_eq_two {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    [finite_dimensional ℝ ↥(affine_subspace.direction s)]
    (hd : finite_dimensional.findim ℝ ↥(affine_subspace.direction s) = bit0 1) {c₁ : P} {c₂ : P}
    {p₁ : P} {p₂ : P} {p : P} (hc₁s : c₁ ∈ s) (hc₂s : c₂ ∈ s) (hp₁s : p₁ ∈ s) (hp₂s : p₂ ∈ s)
    (hps : p ∈ s) {r₁ : ℝ} {r₂ : ℝ} (hc : c₁ ≠ c₂) (hp : p₁ ≠ p₂) (hp₁c₁ : dist p₁ c₁ = r₁)
    (hp₂c₁ : dist p₂ c₁ = r₁) (hpc₁ : dist p c₁ = r₁) (hp₁c₂ : dist p₁ c₂ = r₂)
    (hp₂c₂ : dist p₂ c₂ = r₂) (hpc₂ : dist p c₂ = r₂) : p = p₁ ∨ p = p₂ :=
  sorry

/-- Distances `r₁` `r₂` of `p` from two different points `c₁` `c₂` determine at
most two points `p₁` `p₂` in two-dimensional space (two circles intersect in at
most two points). -/
theorem eq_of_dist_eq_of_dist_eq_of_findim_eq_two {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] [finite_dimensional ℝ V]
    (hd : finite_dimensional.findim ℝ V = bit0 1) {c₁ : P} {c₂ : P} {p₁ : P} {p₂ : P} {p : P}
    {r₁ : ℝ} {r₂ : ℝ} (hc : c₁ ≠ c₂) (hp : p₁ ≠ p₂) (hp₁c₁ : dist p₁ c₁ = r₁)
    (hp₂c₁ : dist p₂ c₁ = r₁) (hpc₁ : dist p c₁ = r₁) (hp₁c₂ : dist p₁ c₂ = r₂)
    (hp₂c₂ : dist p₂ c₂ = r₂) (hpc₂ : dist p c₂ = r₂) : p = p₁ ∨ p = p₂ :=
  sorry

/-- The orthogonal projection of a point onto a nonempty affine
subspace, whose direction is complete, as an unbundled function.  This
definition is only intended for use in setting up the bundled version
`orthogonal_projection` and should not be used once that is
defined. -/
def orthogonal_projection_fn {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] (s : affine_subspace ℝ P) [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] (p : P) : P :=
  classical.some sorry

/-- The intersection of the subspace and the orthogonal subspace
through the given point is the `orthogonal_projection_fn` of that
point onto the subspace.  This lemma is only intended for use in
setting up the bundled version and should not be used once that is
defined. -/
theorem inter_eq_singleton_orthogonal_projection_fn {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] (p : P) :
    ↑s ∩ ↑(affine_subspace.mk' p (affine_subspace.direction sᗮ)) =
        singleton (orthogonal_projection_fn s p) :=
  sorry

/-- The `orthogonal_projection_fn` lies in the given subspace.  This
lemma is only intended for use in setting up the bundled version and
should not be used once that is defined. -/
theorem orthogonal_projection_fn_mem {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P} [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] (p : P) : orthogonal_projection_fn s p ∈ s :=
  sorry

/-- The `orthogonal_projection_fn` lies in the orthogonal
subspace.  This lemma is only intended for use in setting up the
bundled version and should not be used once that is defined. -/
theorem orthogonal_projection_fn_mem_orthogonal {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] (p : P) :
    orthogonal_projection_fn s p ∈ affine_subspace.mk' p (affine_subspace.direction sᗮ) :=
  sorry

/-- Subtracting `p` from its `orthogonal_projection_fn` produces a
result in the orthogonal direction.  This lemma is only intended for
use in setting up the bundled version and should not be used once that
is defined. -/
theorem orthogonal_projection_fn_vsub_mem_direction_orthogonal {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] (p : P) :
    orthogonal_projection_fn s p -ᵥ p ∈ (affine_subspace.direction sᗮ) :=
  affine_subspace.direction_mk' p (affine_subspace.direction sᗮ) ▸
    affine_subspace.vsub_mem_direction (orthogonal_projection_fn_mem_orthogonal p)
      (affine_subspace.self_mem_mk' p (affine_subspace.direction sᗮ))

/-- The orthogonal projection of a point onto a nonempty affine
subspace, whose direction is complete. The corresponding linear map
(mapping a vector to the difference between the projections of two
points whose difference is that vector) is the `orthogonal_projection`
for real inner product spaces, onto the direction of the affine
subspace being projected onto. -/
def orthogonal_projection {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] (s : affine_subspace ℝ P) [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] : affine_map ℝ P ↥s :=
  affine_map.mk
    (fun (p : P) =>
      { val := orthogonal_projection_fn s p, property := orthogonal_projection_fn_mem p })
    ↑(orthogonal_projection (affine_subspace.direction s)) sorry

@[simp] theorem orthogonal_projection_fn_eq {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P} [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] (p : P) :
    orthogonal_projection_fn s p = ↑(coe_fn (orthogonal_projection s) p) :=
  rfl

/-- The linear map corresponding to `orthogonal_projection`. -/
@[simp] theorem orthogonal_projection_linear {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P} [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] :
    affine_map.linear (orthogonal_projection s) =
        ↑(orthogonal_projection (affine_subspace.direction s)) :=
  rfl

/-- The intersection of the subspace and the orthogonal subspace
through the given point is the `orthogonal_projection` of that point
onto the subspace. -/
theorem inter_eq_singleton_orthogonal_projection {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] (p : P) :
    ↑s ∩ ↑(affine_subspace.mk' p (affine_subspace.direction sᗮ)) =
        singleton ↑(coe_fn (orthogonal_projection s) p) :=
  sorry

/-- The `orthogonal_projection` lies in the given subspace. -/
theorem orthogonal_projection_mem {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P} [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] (p : P) :
    ↑(coe_fn (orthogonal_projection s) p) ∈ s :=
  subtype.property (coe_fn (orthogonal_projection s) p)

/-- The `orthogonal_projection` lies in the orthogonal subspace. -/
theorem orthogonal_projection_mem_orthogonal {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] (s : affine_subspace ℝ P) [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] (p : P) :
    ↑(coe_fn (orthogonal_projection s) p) ∈ affine_subspace.mk' p (affine_subspace.direction sᗮ) :=
  orthogonal_projection_fn_mem_orthogonal p

/-- Subtracting a point in the given subspace from the
`orthogonal_projection` produces a result in the direction of the
given subspace. -/
theorem orthogonal_projection_vsub_mem_direction {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] {p1 : P} (p2 : P) (hp1 : p1 ∈ s) :
    ↑(coe_fn (orthogonal_projection s) p2 -ᵥ { val := p1, property := hp1 }) ∈
        affine_subspace.direction s :=
  subtype.property (coe_fn (orthogonal_projection s) p2 -ᵥ { val := p1, property := hp1 })

/-- Subtracting the `orthogonal_projection` from a point in the given
subspace produces a result in the direction of the given subspace. -/
theorem vsub_orthogonal_projection_mem_direction {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] {p1 : P} (p2 : P) (hp1 : p1 ∈ s) :
    ↑({ val := p1, property := hp1 } -ᵥ coe_fn (orthogonal_projection s) p2) ∈
        affine_subspace.direction s :=
  subtype.property ({ val := p1, property := hp1 } -ᵥ coe_fn (orthogonal_projection s) p2)

/-- A point equals its orthogonal projection if and only if it lies in
the subspace. -/
theorem orthogonal_projection_eq_self_iff {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P} [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] {p : P} :
    ↑(coe_fn (orthogonal_projection s) p) = p ↔ p ∈ s :=
  sorry

/-- Orthogonal projection is idempotent. -/
@[simp] theorem orthogonal_projection_orthogonal_projection {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] (s : affine_subspace ℝ P)
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] (p : P) :
    coe_fn (orthogonal_projection s) ↑(coe_fn (orthogonal_projection s) p) =
        coe_fn (orthogonal_projection s) p :=
  sorry

theorem eq_orthogonal_projection_of_eq_subspace {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    {s' : affine_subspace ℝ P} [Nonempty ↥s] [Nonempty ↥s']
    [complete_space ↥(affine_subspace.direction s)] [complete_space ↥(affine_subspace.direction s')]
    (h : s = s') (p : P) :
    ↑(coe_fn (orthogonal_projection s) p) = ↑(coe_fn (orthogonal_projection s') p) :=
  sorry

/-- The distance to a point's orthogonal projection is 0 iff it lies in the subspace. -/
theorem dist_orthogonal_projection_eq_zero_iff {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] {p : P} :
    dist p ↑(coe_fn (orthogonal_projection s) p) = 0 ↔ p ∈ s :=
  sorry

/-- The distance between a point and its orthogonal projection is
nonzero if it does not lie in the subspace. -/
theorem dist_orthogonal_projection_ne_zero_of_not_mem {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] {p : P} (hp : ¬p ∈ s) :
    dist p ↑(coe_fn (orthogonal_projection s) p) ≠ 0 :=
  mt (iff.mp dist_orthogonal_projection_eq_zero_iff) hp

/-- Subtracting `p` from its `orthogonal_projection` produces a result
in the orthogonal direction. -/
theorem orthogonal_projection_vsub_mem_direction_orthogonal {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] (s : affine_subspace ℝ P)
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] (p : P) :
    ↑(coe_fn (orthogonal_projection s) p) -ᵥ p ∈ (affine_subspace.direction sᗮ) :=
  orthogonal_projection_fn_vsub_mem_direction_orthogonal p

/-- Subtracting the `orthogonal_projection` from `p` produces a result
in the orthogonal direction. -/
theorem vsub_orthogonal_projection_mem_direction_orthogonal {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] (s : affine_subspace ℝ P)
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] (p : P) :
    p -ᵥ ↑(coe_fn (orthogonal_projection s) p) ∈ (affine_subspace.direction sᗮ) :=
  affine_subspace.direction_mk' p (affine_subspace.direction sᗮ) ▸
    affine_subspace.vsub_mem_direction
      (affine_subspace.self_mem_mk' p (affine_subspace.direction sᗮ))
      (orthogonal_projection_mem_orthogonal s p)

/-- Adding a vector to a point in the given subspace, then taking the
orthogonal projection, produces the original point if the vector was
in the orthogonal direction. -/
theorem orthogonal_projection_vadd_eq_self {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P} [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] {p : P} (hp : p ∈ s) {v : V}
    (hv : v ∈ (affine_subspace.direction sᗮ)) :
    coe_fn (orthogonal_projection s) (v +ᵥ p) = { val := p, property := hp } :=
  sorry

/-- Adding a vector to a point in the given subspace, then taking the
orthogonal projection, produces the original point if the vector is a
multiple of the result of subtracting a point's orthogonal projection
from that point. -/
theorem orthogonal_projection_vadd_smul_vsub_orthogonal_projection {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] {p1 : P} (p2 : P) (r : ℝ)
    (hp : p1 ∈ s) :
    coe_fn (orthogonal_projection s) (r • (p2 -ᵥ ↑(coe_fn (orthogonal_projection s) p2)) +ᵥ p1) =
        { val := p1, property := hp } :=
  orthogonal_projection_vadd_eq_self hp
    (submodule.smul_mem (affine_subspace.direction sᗮ) r
      (vsub_orthogonal_projection_mem_direction_orthogonal s p2))

/-- The square of the distance from a point in `s` to `p2` equals the
sum of the squares of the distances of the two points to the
`orthogonal_projection`. -/
theorem dist_square_eq_dist_orthogonal_projection_square_add_dist_orthogonal_projection_square
    {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P]
    {s : affine_subspace ℝ P} [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] {p1 : P}
    (p2 : P) (hp1 : p1 ∈ s) :
    dist p1 p2 * dist p1 p2 =
        dist p1 ↑(coe_fn (orthogonal_projection s) p2) *
            dist p1 ↑(coe_fn (orthogonal_projection s) p2) +
          dist p2 ↑(coe_fn (orthogonal_projection s) p2) *
            dist p2 ↑(coe_fn (orthogonal_projection s) p2) :=
  sorry

/-- The square of the distance between two points constructed by
adding multiples of the same orthogonal vector to points in the same
subspace. -/
theorem dist_square_smul_orthogonal_vadd_smul_orthogonal_vadd {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    {p1 : P} {p2 : P} (hp1 : p1 ∈ s) (hp2 : p2 ∈ s) (r1 : ℝ) (r2 : ℝ) {v : V}
    (hv : v ∈ (affine_subspace.direction sᗮ)) :
    dist (r1 • v +ᵥ p1) (r2 • v +ᵥ p2) * dist (r1 • v +ᵥ p1) (r2 • v +ᵥ p2) =
        dist p1 p2 * dist p1 p2 + (r1 - r2) * (r1 - r2) * (norm v * norm v) :=
  sorry

/-- Reflection in an affine subspace, which is expected to be nonempty
and complete.  The word "reflection" is sometimes understood to mean
specifically reflection in a codimension-one subspace, and sometimes
more generally to cover operations such as reflection in a point.  The
definition here, of reflection in an affine subspace, is a more
general sense of the word that includes both those common cases.  If
the subspace is empty or not complete, `orthogonal_projection` is
defined as the identity map, which results in `reflection` being the
identity map in that case as well. -/
def reflection {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] (s : affine_subspace ℝ P) [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] : P ≃ᵢ P :=
  isometric.mk
    (equiv.mk
      (fun (p : P) =>
        ↑(coe_fn (orthogonal_projection s) p) -ᵥ p +ᵥ ↑(coe_fn (orthogonal_projection s) p))
      (fun (p : P) =>
        ↑(coe_fn (orthogonal_projection s) p) -ᵥ p +ᵥ ↑(coe_fn (orthogonal_projection s) p))
      sorry sorry)
    sorry

/-- The result of reflecting. -/
theorem reflection_apply {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] (s : affine_subspace ℝ P) [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] (p : P) :
    coe_fn (reflection s) p =
        ↑(coe_fn (orthogonal_projection s) p) -ᵥ p +ᵥ ↑(coe_fn (orthogonal_projection s) p) :=
  rfl

theorem eq_reflection_of_eq_subspace {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P} {s' : affine_subspace ℝ P}
    [Nonempty ↥s] [Nonempty ↥s'] [complete_space ↥(affine_subspace.direction s)]
    [complete_space ↥(affine_subspace.direction s')] (h : s = s') (p : P) :
    coe_fn (reflection s) p = coe_fn (reflection s') p :=
  sorry

/-- Reflection is its own inverse. -/
@[simp] theorem reflection_symm {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] (s : affine_subspace ℝ P) [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] :
    isometric.symm (reflection s) = reflection s :=
  rfl

/-- Reflecting twice in the same subspace. -/
@[simp] theorem reflection_reflection {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] (s : affine_subspace ℝ P) [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] (p : P) :
    coe_fn (reflection s) (coe_fn (reflection s) p) = p :=
  equiv.left_inv (isometric.to_equiv (reflection s)) p

/-- Reflection is involutive. -/
theorem reflection_involutive {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] (s : affine_subspace ℝ P) [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] : function.involutive ⇑(reflection s) :=
  reflection_reflection s

/-- A point is its own reflection if and only if it is in the
subspace. -/
theorem reflection_eq_self_iff {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P} [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] (p : P) : coe_fn (reflection s) p = p ↔ p ∈ s :=
  sorry

/-- Reflecting a point in two subspaces produces the same result if
and only if the point has the same orthogonal projection in each of
those subspaces. -/
theorem reflection_eq_iff_orthogonal_projection_eq {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] (s₁ : affine_subspace ℝ P)
    (s₂ : affine_subspace ℝ P) [Nonempty ↥s₁] [Nonempty ↥s₂]
    [complete_space ↥(affine_subspace.direction s₁)]
    [complete_space ↥(affine_subspace.direction s₂)] (p : P) :
    coe_fn (reflection s₁) p = coe_fn (reflection s₂) p ↔
        ↑(coe_fn (orthogonal_projection s₁) p) = ↑(coe_fn (orthogonal_projection s₂) p) :=
  sorry

/-- The distance between `p₁` and the reflection of `p₂` equals that
between the reflection of `p₁` and `p₂`. -/
theorem dist_reflection {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] (s : affine_subspace ℝ P) [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] (p₁ : P) (p₂ : P) :
    dist p₁ (coe_fn (reflection s) p₂) = dist (coe_fn (reflection s) p₁) p₂ :=
  sorry

/-- A point in the subspace is equidistant from another point and its
reflection. -/
theorem dist_reflection_eq_of_mem {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] (s : affine_subspace ℝ P) [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] {p₁ : P} (hp₁ : p₁ ∈ s) (p₂ : P) :
    dist p₁ (coe_fn (reflection s) p₂) = dist p₁ p₂ :=
  sorry

/-- The reflection of a point in a subspace is contained in any larger
subspace containing both the point and the subspace reflected in. -/
theorem reflection_mem_of_le_of_mem {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {s₁ : affine_subspace ℝ P} {s₂ : affine_subspace ℝ P}
    [Nonempty ↥s₁] [complete_space ↥(affine_subspace.direction s₁)] (hle : s₁ ≤ s₂) {p : P}
    (hp : p ∈ s₂) : coe_fn (reflection s₁) p ∈ s₂ :=
  eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn (reflection s₁) p ∈ s₂)) (reflection_apply s₁ p)))
    (affine_subspace.vadd_mem_of_mem_direction
      (affine_subspace.vsub_mem_direction (hle (orthogonal_projection_mem p)) hp)
      (hle (orthogonal_projection_mem p)))

/-- Reflecting an orthogonal vector plus a point in the subspace
produces the negation of that vector plus the point. -/
theorem reflection_orthogonal_vadd {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P} [Nonempty ↥s]
    [complete_space ↥(affine_subspace.direction s)] {p : P} (hp : p ∈ s) {v : V}
    (hv : v ∈ (affine_subspace.direction sᗮ)) : coe_fn (reflection s) (v +ᵥ p) = -v +ᵥ p :=
  sorry

/-- Reflecting a vector plus a point in the subspace produces the
negation of that vector plus the point if the vector is a multiple of
the result of subtracting a point's orthogonal projection from that
point. -/
theorem reflection_vadd_smul_vsub_orthogonal_projection {V : Type u_1} {P : Type u_2}
    [inner_product_space ℝ V] [metric_space P] [normed_add_torsor V P] {s : affine_subspace ℝ P}
    [Nonempty ↥s] [complete_space ↥(affine_subspace.direction s)] {p₁ : P} (p₂ : P) (r : ℝ)
    (hp₁ : p₁ ∈ s) :
    coe_fn (reflection s) (r • (p₂ -ᵥ ↑(coe_fn (orthogonal_projection s) p₂)) +ᵥ p₁) =
        -(r • (p₂ -ᵥ ↑(coe_fn (orthogonal_projection s) p₂))) +ᵥ p₁ :=
  reflection_orthogonal_vadd hp₁
    (submodule.smul_mem (affine_subspace.direction sᗮ) r
      (vsub_orthogonal_projection_mem_direction_orthogonal s p₂))

/-- A set of points is cospherical if they are equidistant from some
point.  In two dimensions, this is the same thing as being
concyclic. -/
def cospherical {P : Type u_2} [metric_space P] (ps : set P) :=
  ∃ (center : P), ∃ (radius : ℝ), ∀ (p : P), p ∈ ps → dist p center = radius

/-- The definition of `cospherical`. -/
theorem cospherical_def {P : Type u_2} [metric_space P] (ps : set P) :
    cospherical ps ↔ ∃ (center : P), ∃ (radius : ℝ), ∀ (p : P), p ∈ ps → dist p center = radius :=
  iff.rfl

/-- A subset of a cospherical set is cospherical. -/
theorem cospherical_subset {P : Type u_2} [metric_space P] {ps₁ : set P} {ps₂ : set P}
    (hs : ps₁ ⊆ ps₂) (hc : cospherical ps₂) : cospherical ps₁ :=
  sorry

/-- The empty set is cospherical. -/
theorem cospherical_empty {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V] [metric_space P]
    [normed_add_torsor V P] : cospherical ∅ :=
  sorry

/-- A single point is cospherical. -/
theorem cospherical_singleton {P : Type u_2} [metric_space P] (p : P) : cospherical (singleton p) :=
  sorry

/-- Two points are cospherical. -/
theorem cospherical_insert_singleton {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] (p₁ : P) (p₂ : P) :
    cospherical (insert p₁ (singleton p₂)) :=
  sorry

/-- Any three points in a cospherical set are affinely independent. -/
theorem cospherical.affine_independent {V : Type u_1} {P : Type u_2} [inner_product_space ℝ V]
    [metric_space P] [normed_add_torsor V P] {s : set P} (hs : cospherical s) {p : fin (bit1 1) → P}
    (hps : set.range p ⊆ s) (hpi : function.injective p) : affine_independent ℝ p :=
  sorry

end Mathlib


theorem false_implies_false : false → false :=
begin
  intro Hfalse,
  exact Hfalse,
end


/-
Copyright (c) 2020 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes
-/
import data.equiv.mul_add_aut
import logic.function.basic
import group_theory.subgroup.basic

/-!
# Semidirect product

This file defines semidirect products of groups, and the canonical maps in and out of the
semidirect product. The semidirect product of `N` and `G` given a hom `φ` from
`φ` from `G` to the automorphism group of `N` is the product of sets with the group
`⟨n₁, g₁⟩ * ⟨n₂, g₂⟩ = ⟨n₁ * φ g₁ n₂, g₁ * g₂⟩`

## Key definitions

There are two homs into the semidirect product `inl : N →* N ⋊[φ] G` and
`inr : G →* N ⋊[φ] G`, and `lift` can be used to define maps `N ⋊[φ] G →* H`
out of the semidirect product given maps `f₁ : N →* H` and `f₂ : G →* H` that satisfy the
condition `∀ n g, f₁ (φ g n) = f₂ g * f₁ n * f₂ g⁻¹`

## Notation

This file introduces the global notation `N ⋊[φ] G` for `semidirect_product N G φ`

## Tags
group, semidirect product
-/
variables (N : Type*) (G : Type*) {H : Type*} [group N] [group G] [group H]

/-- The semidirect product of groups `N` and `G`, given a map `φ` from `G` to the automorphism
  group of `N`. It the product of sets with the group operation
  `⟨n₁, g₁⟩ * ⟨n₂, g₂⟩ = ⟨n₁ * φ g₁ n₂, g₁ * g₂⟩` -/
@[ext, derive decidable_eq]
structure semidirect_product (φ : G →* mul_aut N) :=
(left : N) (right : G)

attribute [pp_using_anonymous_constructor] semidirect_product

notation N` ⋊[`:35 φ:35`] `:0 G :35 := semidirect_product N G φ

namespace semidirect_product

variables {N G} {φ : G →* mul_aut N}

private def one_aux : N ⋊[φ] G := ⟨1, 1⟩
private def mul_aux (a b : N ⋊[φ] G) : N ⋊[φ] G := ⟨a.1 * φ a.2 b.1, a.right * b.right⟩
private def inv_aux (a : N ⋊[φ] G) : N ⋊[φ] G := let i := a.2⁻¹ in ⟨φ i a.1⁻¹, i⟩
private lemma mul_assoc_aux (a b c : N ⋊[φ] G) :
  mul_aux (mul_aux a b) c = mul_aux a (mul_aux b c) :=
by simp [mul_aux, mul_assoc, mul_equiv.map_mul]
private lemma mul_one_aux (a : N ⋊[φ] G) : mul_aux a one_aux = a :=
by cases a; simp [mul_aux, one_aux]
private lemma one_mul_aux (a : N ⋊[φ] G) : mul_aux one_aux a = a :=
by cases a; simp [mul_aux, one_aux]
private lemma mul_left_inv_aux (a : N ⋊[φ] G) : mul_aux (inv_aux a) a = one_aux :=
by simp only [mul_aux, inv_aux, one_aux, ← mul_equiv.map_mul, mul_left_inv]; simp

instance : group (N ⋊[φ] G) :=
{ one := one_aux,
  inv := inv_aux,
  mul := mul_aux,
  mul_assoc := mul_assoc_aux,
  one_mul := one_mul_aux,
  mul_one := mul_one_aux,
  mul_left_inv := mul_left_inv_aux }

instance : inhabited (N ⋊[φ] G) := ⟨1⟩

@[simp] lemma one_left : (1 : N ⋊[φ] G).left = 1 := rfl
@[simp] lemma one_right : (1 : N ⋊[φ] G).right = 1 := rfl
@[simp] lemma inv_left (a : N ⋊[φ] G) : (a⁻¹).left = φ a.right⁻¹ a.left⁻¹ := rfl
@[simp] lemma inv_right (a : N ⋊[φ] G) : (a⁻¹).right = a.right⁻¹ := rfl
@[simp] lemma mul_left (a b : N ⋊[φ] G) : (a * b).left = a.left * φ a.right b.left := rfl
@[simp] lemma mul_right (a b : N ⋊[φ] G) : (a * b).right = a.right * b.right := rfl

/-- The canonical map `N →* N ⋊[φ] G` sending `n` to `⟨n, 1⟩` -/
def inl : N →* N ⋊[φ] G :=
{ to_fun := λ n, ⟨n, 1⟩,
  map_one' := rfl,
  map_mul' := by intros; ext; simp }

@[simp] lemma left_inl (n : N) : (inl n : N ⋊[φ] G).left = n := rfl
@[simp] lemma right_inl (n : N) : (inl n : N ⋊[φ] G).right = 1 := rfl

lemma inl_injective : function.injective (inl : N → N ⋊[φ] G) :=
function.injective_iff_has_left_inverse.2 ⟨left, left_inl⟩

@[simp] lemma inl_inj {n₁ n₂ : N} : (inl n₁ : N ⋊[φ] G) = inl n₂ ↔ n₁ = n₂ :=
inl_injective.eq_iff

/-- The canonical map `G →* N ⋊[φ] G` sending `g` to `⟨1, g⟩` -/
def inr : G →* N ⋊[φ] G :=
{ to_fun := λ g, ⟨1, g⟩,
  map_one' := rfl,
  map_mul' := by intros; ext; simp }

@[simp] lemma left_inr (g : G) : (inr g : N ⋊[φ] G).left = 1 := rfl
@[simp] lemma right_inr (g : G) : (inr g : N ⋊[φ] G).right = g := rfl

lemma inr_injective : function.injective (inr : G → N ⋊[φ] G) :=
function.injective_iff_has_left_inverse.2 ⟨right, right_inr⟩

@[simp] lemma inr_inj {g₁ g₂ : G} : (inr g₁ : N ⋊[φ] G) = inr g₂ ↔ g₁ = g₂ :=
inr_injective.eq_iff

lemma inl_aut (g : G) (n : N) : (inl (φ g n) : N ⋊[φ] G) = inr g * inl n * inr g⁻¹ :=
by ext; simp

lemma inl_aut_inv (g : G) (n : N) : (inl ((φ g)⁻¹ n) : N ⋊[φ] G) = inr g⁻¹ * inl n * inr g :=
by rw [← monoid_hom.map_inv, inl_aut, inv_inv]

@[simp]

@[simp] lemma inl_left_mul_inr_right (x : N ⋊[φ] G) : inl x.left * inr x.right = x :=

by ext; simp

/-- The canonical projection map `N ⋊[φ] G →* G`, as a group hom. -/
def right_hom : N ⋊[φ] G →* G :=
{ to_fun := semidirect_product.right,
  map_one' := rfl,
  map_mul' := λ _ _, rfl }

@[simp] lemma right_hom_eq_right : (right_hom :  N ⋊[φ] G → G) = right := rfl

@[simp] lemma right_hom_comp_inl : (right_hom : N ⋊[φ] G →* G).comp inl = 1 :=
by ext; simp [right_hom]

@[simp] lemma right_hom_comp_inr : (right_hom : N ⋊[φ] G →* G).comp inr = monoid_hom.id _ :=
by ext; simp [right_hom]

@[simp] lemma right_hom_inl (n : N) : right_hom (inl n : N ⋊[φ] G) = 1 :=
by simp [right_hom]

@[simp] lemma right_hom_inr (g : G) : right_hom (inr g : N ⋊[φ] G) = g :=
by simp [right_hom]

lemma right_hom_surjective : function.surjective (right_hom : N ⋊[φ] G → G) :=
function.surjective_iff_has_right_inverse.2 ⟨inr, right_hom_inr⟩

lemma range_inl_eq_ker_right_hom : (inl : N →* N ⋊[φ] G).range = right_hom.ker :=
le_antisymm
  (λ _, by simp [monoid_hom.mem_ker, eq_comm] {contextual := tt})
  (λ x hx, ⟨x.left, by ext; simp [*, monoid_hom.mem_ker] at *⟩)

section lift
variables (f₁ : N →* H) (f₂ : G →* H)
  (h : ∀ g, f₁.comp (φ g).to_monoid_hom = (mul_aut.conj (f₂ g)).to_monoid_hom.comp f₁)

/-- Define a group hom `N ⋊[φ] G →* H`, by defining maps `N →* H` and `G →* H`  -/
def lift (f₁ : N →* H) (f₂ : G →* H)
  (h : ∀ g, f₁.comp (φ g).to_monoid_hom = (mul_aut.conj (f₂ g)).to_monoid_hom.comp f₁) :
  N ⋊[φ] G →* H :=
{ to_fun := λ a, f₁ a.1 * f₂ a.2,
  map_one' := by simp,
  map_mul' := λ a b, begin
    have := λ n g, monoid_hom.ext_iff.1 (h n) g,
    simp only [mul_aut.conj_apply, monoid_hom.comp_apply, mul_equiv.coe_to_monoid_hom] at this,
    simp [this, mul_assoc]
  end }

@[simp] lemma lift_inl (n : N) : lift f₁ f₂ h (inl n) = f₁ n := by simp [lift]
@[simp] lemma lift_comp_inl : (lift f₁ f₂ h).comp inl = f₁ := by ext; simp

@[simp] lemma lift_inr (g : G) : lift f₁ f₂ h (inr g) = f₂ g := by simp [lift]
@[simp] lemma lift_comp_inr : (lift f₁ f₂ h).comp inr = f₂ := by ext; simp

lemma lift_unique (F : N ⋊[φ] G →* H) :
  F = lift (F.comp inl) (F.comp inr) (λ _, by ext; simp [inl_aut]) :=
begin
  ext,
  simp only [lift, monoid_hom.comp_apply, monoid_hom.coe_mk],
  rw [← F.map_mul, inl_left_mul_inr_right],
end

/-- Two maps out of the semidirect product are equal if they're equal after composition
  with both `inl` and `inr` -/
lemma hom_ext {f g : (N ⋊[φ] G) →* H} (hl : f.comp inl = g.comp inl)
  (hr : f.comp inr = g.comp inr) : f = g :=
by { rw [lift_unique f, lift_unique g], simp only * }

end lift

section map

variables {N₁ : Type*} {G₁ : Type*} [group N₁] [group G₁] {φ₁ : G₁ →* mul_aut N₁}

/-- Define a map from `N ⋊[φ] G` to `N₁ ⋊[φ₁] G₁` given maps `N →* N₁` and `G →* G₁` that
  satisfy a commutativity condition `∀ n g, f₁ (φ g n) = φ₁ (f₂ g) (f₁ n)`.  -/
def map (f₁ : N →* N₁) (f₂ : G →* G₁)
  (h : ∀ g : G, f₁.comp (φ g).to_monoid_hom = (φ₁ (f₂ g)).to_monoid_hom.comp f₁) :
  N ⋊[φ] G →* N₁ ⋊[φ₁] G₁ :=
{ to_fun := λ x, ⟨f₁ x.1, f₂ x.2⟩,
  map_one' := by simp,
  map_mul' := λ x y, begin
    replace h := monoid_hom.ext_iff.1 (h x.right) y.left,
    ext; simp * at *,
  end }

variables (f₁ : N →* N₁) (f₂ : G →* G₁)
  (h : ∀ g : G, f₁.comp (φ g).to_monoid_hom = (φ₁ (f₂ g)).to_monoid_hom.comp f₁)

@[simp] lemma map_left (g : N ⋊[φ] G) : (map f₁ f₂ h g).left = f₁ g.left := rfl

@[simp] lemma map_right (g : N ⋊[φ] G) : (map f₁ f₂ h g).right = f₂ g.right := rfl

@[simp] lemma right_hom_comp_map : right_hom.comp (map f₁ f₂ h) = f₂.comp right_hom := rfl

@[simp] lemma map_inl (n : N) : map f₁ f₂ h (inl n) = inl (f₁ n) :=
by simp [map]

@[simp] lemma map_comp_inl : (map f₁ f₂ h).comp inl = inl.comp f₁ :=
by ext; simp

@[simp] lemma map_inr (g : G) : map f₁ f₂ h (inr g) = inr (f₂ g) :=
by simp [map]

@[simp] lemma map_comp_inr : (map f₁ f₂ h).comp inr = inr.comp f₂ :=
by ext; simp [map]

end map

end semidirect_product


/-
Copyright (c) 2020 Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bhavik Mehta
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.list.chain
import Mathlib.category_theory.punit
import Mathlib.PostPort

universes v₁ u₁ l u_1 v₂ u₂ u_2

namespace Mathlib

/-!
# Connected category

Define a connected category as a _nonempty_ category for which every functor
to a discrete category is isomorphic to the constant functor.

NB. Some authors include the empty category as connected, we do not.
We instead are interested in categories with exactly one 'connected
component'.

We give some equivalent definitions:
- A nonempty category for which every functor to a discrete category is
  constant on objects.
  See `any_functor_const_on_obj` and `connected.of_any_functor_const_on_obj`.
- A nonempty category for which every function `F` for which the presence of a
  morphism `f : j₁ ⟶ j₂` implies `F j₁ = F j₂` must be constant everywhere.
  See `constant_of_preserves_morphisms` and `connected.of_constant_of_preserves_morphisms`.
- A nonempty category for which any subset of its elements containing the
  default and closed under morphisms is everything.
  See `induct_on_objects` and `connected.of_induct`.
- A nonempty category for which every object is related under the reflexive
  transitive closure of the relation "there is a morphism in some direction
  from `j₁` to `j₂`".
  See `connected_zigzag` and `zigzag_connected`.
- A nonempty category for which for any two objects there is a sequence of
  morphisms (some reversed) from one to the other.
  See `exists_zigzag'` and `connected_of_zigzag`.

We also prove the result that the functor given by `(X × -)` preserves any
connected limit. That is, any limit of shape `J` where `J` is a connected
category is preserved by the functor `(X × -)`. This appears in `category_theory.limits.connected`.
-/

namespace category_theory

/--
A possibly empty category for which every functor to a discrete category is constant.
-/
class is_preconnected (J : Type u₁) [category J] where
  iso_constant :
    ∀ {α : Type u₁} (F : J ⥤ discrete α) (j : J),
      Nonempty (F ≅ functor.obj (functor.const J) (functor.obj F j))

/--
We define a connected category as a _nonempty_ category for which every
functor to a discrete category is constant.

NB. Some authors include the empty category as connected, we do not.
We instead are interested in categories with exactly one 'connected
component'.

This allows us to show that the functor X ⨯ - preserves connected limits.

See https://stacks.math.columbia.edu/tag/002S
-/
class is_connected (J : Type u₁) [category J] extends is_preconnected J where
  is_nonempty : Nonempty J

/--
If `J` is connected, any functor `F : J ⥤ discrete α` is isomorphic to
the constant functor with value `F.obj j` (for any choice of `j`).
-/
def iso_constant {J : Type u₁} [category J] [is_preconnected J] {α : Type u₁} (F : J ⥤ discrete α)
    (j : J) : F ≅ functor.obj (functor.const J) (functor.obj F j) :=
  nonempty.some (is_preconnected.iso_constant F j)

/--
If J is connected, any functor to a discrete category is constant on objects.
The converse is given in `is_connected.of_any_functor_const_on_obj`.
-/
theorem any_functor_const_on_obj {J : Type u₁} [category J] [is_preconnected J] {α : Type u₁}
    (F : J ⥤ discrete α) (j : J) (j' : J) : functor.obj F j = functor.obj F j' :=
  plift.down (ulift.down (nat_trans.app (iso.hom (iso_constant F j')) j))

/--
If any functor to a discrete category is constant on objects, J is connected.
The converse of `any_functor_const_on_obj`.
-/
theorem is_connected.of_any_functor_const_on_obj {J : Type u₁} [category J] [Nonempty J]
    (h : ∀ {α : Type u₁} (F : J ⥤ discrete α) (j j' : J), functor.obj F j = functor.obj F j') :
    is_connected J :=
  is_connected.mk

/--
If `J` is connected, then given any function `F` such that the presence of a
morphism `j₁ ⟶ j₂` implies `F j₁ = F j₂`, we have that `F` is constant.
This can be thought of as a local-to-global property.

The converse is shown in `is_connected.of_constant_of_preserves_morphisms`
-/
theorem constant_of_preserves_morphisms {J : Type u₁} [category J] [is_preconnected J] {α : Type u₁}
    (F : J → α) (h : ∀ (j₁ j₂ : J), (j₁ ⟶ j₂) → F j₁ = F j₂) (j : J) (j' : J) : F j = F j' :=
  any_functor_const_on_obj
    (functor.mk F fun (_x _x_1 : J) (f : _x ⟶ _x_1) => eq_to_hom (h _x _x_1 f)) j j'

/--
`J` is connected if: given any function `F : J → α` which is constant for any
`j₁, j₂` for which there is a morphism `j₁ ⟶ j₂`, then `F` is constant.
This can be thought of as a local-to-global property.

The converse of `constant_of_preserves_morphisms`.
-/
theorem is_connected.of_constant_of_preserves_morphisms {J : Type u₁} [category J] [Nonempty J]
    (h :
      ∀ {α : Type u₁} (F : J → α),
        (∀ {j₁ j₂ : J}, (j₁ ⟶ j₂) → F j₁ = F j₂) → ∀ (j j' : J), F j = F j') :
    is_connected J :=
  is_connected.of_any_functor_const_on_obj
    fun (_x : Type u₁) (F : J ⥤ discrete _x) =>
      h (functor.obj F)
        fun (_x_1 _x_2 : J) (f : _x_1 ⟶ _x_2) => plift.down (ulift.down (functor.map F f))

/--
An inductive-like property for the objects of a connected category.
If the set `p` is nonempty, and `p` is closed under morphisms of `J`,
then `p` contains all of `J`.

The converse is given in `is_connected.of_induct`.
-/
theorem induct_on_objects {J : Type u₁} [category J] [is_preconnected J] (p : set J) {j₀ : J}
    (h0 : j₀ ∈ p) (h1 : ∀ {j₁ j₂ : J}, (j₁ ⟶ j₂) → (j₁ ∈ p ↔ j₂ ∈ p)) (j : J) : j ∈ p :=
  sorry

/--
If any maximal connected component containing some element j₀ of J is all of J, then J is connected.

The converse of `induct_on_objects`.
-/
theorem is_connected.of_induct {J : Type u₁} [category J] [Nonempty J] {j₀ : J}
    (h :
      ∀ (p : set J), j₀ ∈ p → (∀ {j₁ j₂ : J}, (j₁ ⟶ j₂) → (j₁ ∈ p ↔ j₂ ∈ p)) → ∀ (j : J), j ∈ p) :
    is_connected J :=
  sorry

/--
Another induction principle for `is_preconnected J`:
given a type family `Z : J → Sort*` and
a rule for transporting in *both* directions along a morphism in `J`,
we can transport an `x : Z j₀` to a point in `Z j` for any `j`.
-/
theorem is_preconnected_induction {J : Type u₁} [category J] [is_preconnected J] (Z : J → Sort u_1)
    (h₁ : {j₁ j₂ : J} → (j₁ ⟶ j₂) → Z j₁ → Z j₂) (h₂ : {j₁ j₂ : J} → (j₁ ⟶ j₂) → Z j₂ → Z j₁)
    {j₀ : J} (x : Z j₀) (j : J) : Nonempty (Z j) :=
  sorry

/-- If `J` and `K` are equivalent, then if `J` is preconnected then `K` is as well. -/
theorem is_preconnected_of_equivalent {J : Type u₁} [category J] {K : Type u₁} [category K]
    [is_preconnected J] (e : J ≌ K) : is_preconnected K :=
  sorry

/-- If `J` and `K` are equivalent, then if `J` is connected then `K` is as well. -/
theorem is_connected_of_equivalent {J : Type u₁} [category J] {K : Type u₁} [category K] (e : J ≌ K)
    [is_connected J] : is_connected K :=
  is_connected.mk

/-- j₁ and j₂ are related by `zag` if there is a morphism between them. -/
def zag {J : Type u₁} [category J] (j₁ : J) (j₂ : J) := Nonempty (j₁ ⟶ j₂) ∨ Nonempty (j₂ ⟶ j₁)

theorem zag_symmetric {J : Type u₁} [category J] : symmetric zag :=
  fun (j₂ j₁ : J) (h : zag j₂ j₁) => or.swap h

/--
`j₁` and `j₂` are related by `zigzag` if there is a chain of
morphisms from `j₁` to `j₂`, with backward morphisms allowed.
-/
def zigzag {J : Type u₁} [category J] : J → J → Prop := relation.refl_trans_gen zag

theorem zigzag_symmetric {J : Type u₁} [category J] : symmetric zigzag :=
  relation.refl_trans_gen.symmetric zag_symmetric

theorem zigzag_equivalence {J : Type u₁} [category J] : equivalence zigzag :=
  mk_equivalence zigzag relation.reflexive_refl_trans_gen zigzag_symmetric
    relation.transitive_refl_trans_gen

/--
The setoid given by the equivalence relation `zigzag`. A quotient for this
setoid is a connected component of the category.
-/
def zigzag.setoid (J : Type u₂) [category J] : setoid J := setoid.mk zigzag zigzag_equivalence

/--
If there is a zigzag from `j₁` to `j₂`, then there is a zigzag from `F j₁` to
`F j₂` as long as `F` is a functor.
-/
theorem zigzag_obj_of_zigzag {J : Type u₁} [category J] {K : Type u₂} [category K] (F : J ⥤ K)
    {j₁ : J} {j₂ : J} (h : zigzag j₁ j₂) : zigzag (functor.obj F j₁) (functor.obj F j₂) :=
  sorry

-- TODO: figure out the right way to generalise this to `zigzag`.

theorem zag_of_zag_obj {J : Type u₁} [category J] {K : Type u₂} [category K] (F : J ⥤ K) [full F]
    {j₁ : J} {j₂ : J} (h : zag (functor.obj F j₁) (functor.obj F j₂)) : zag j₁ j₂ :=
  or.imp (nonempty.map (functor.preimage F)) (nonempty.map (functor.preimage F)) h

/-- Any equivalence relation containing (⟶) holds for all pairs of a connected category. -/
theorem equiv_relation {J : Type u₁} [category J] [is_connected J] (r : J → J → Prop)
    (hr : equivalence r) (h : ∀ {j₁ j₂ : J}, (j₁ ⟶ j₂) → r j₁ j₂) (j₁ : J) (j₂ : J) : r j₁ j₂ :=
  sorry

/-- In a connected category, any two objects are related by `zigzag`. -/
theorem is_connected_zigzag {J : Type u₁} [category J] [is_connected J] (j₁ : J) (j₂ : J) :
    zigzag j₁ j₂ :=
  equiv_relation zigzag zigzag_equivalence
    (fun (_x _x_1 : J) (f : _x ⟶ _x_1) =>
      relation.refl_trans_gen.single (Or.inl (Nonempty.intro f)))
    j₁ j₂

/--
If any two objects in an nonempty category are related by `zigzag`, the category is connected.
-/
theorem zigzag_is_connected {J : Type u₁} [category J] [Nonempty J]
    (h : ∀ (j₁ j₂ : J), zigzag j₁ j₂) : is_connected J :=
  sorry

theorem exists_zigzag' {J : Type u₁} [category J] [is_connected J] (j₁ : J) (j₂ : J) :
    ∃ (l : List J), list.chain zag j₁ l ∧ list.last (j₁ :: l) (list.cons_ne_nil j₁ l) = j₂ :=
  list.exists_chain_of_relation_refl_trans_gen (is_connected_zigzag j₁ j₂)

/--
If any two objects in an nonempty category are linked by a sequence of (potentially reversed)
morphisms, then J is connected.

The converse of `exists_zigzag'`.
-/
theorem is_connected_of_zigzag {J : Type u₁} [category J] [Nonempty J]
    (h :
      ∀ (j₁ j₂ : J),
        ∃ (l : List J), list.chain zag j₁ l ∧ list.last (j₁ :: l) (list.cons_ne_nil j₁ l) = j₂) :
    is_connected J :=
  sorry

/-- If `discrete α` is connected, then `α` is (type-)equivalent to `punit`. -/
def discrete_is_connected_equiv_punit {α : Type (max u_1 u_2)} [is_connected (discrete α)] :
    α ≃ PUnit :=
  discrete.equiv_of_equivalence
    (equivalence.mk' (functor.star α)
      (discrete.functor fun (_x : PUnit) => classical.arbitrary (discrete α))
      (iso_constant 𝟭 (classical.arbitrary (discrete α)))
      (functor.punit_ext
        ((discrete.functor fun (_x : PUnit) => classical.arbitrary (discrete α)) ⋙ functor.star α)
        𝟭))

/--
For objects `X Y : C`, any natural transformation `α : const X ⟶ const Y` from a connected
category must be constant.
This is the key property of connected categories which we use to establish properties about limits.
-/
theorem nat_trans_from_is_connected {J : Type u₁} [category J] {C : Type u₂} [category C]
    [is_preconnected J] {X : C} {Y : C}
    (α : functor.obj (functor.const J) X ⟶ functor.obj (functor.const J) Y) (j : J) (j' : J) :
    nat_trans.app α j = nat_trans.app α j' :=
  sorry

end Mathlib


/-
Copyright (c) 2021 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import dynamics.ergodic.measure_preserving
import combinatorics.pigeonhole

/-!
# Conservative systems

In this file we define `f : α → α` to be a *conservative* system w.r.t a measure `μ` if `f` is
non-singular (`measure_theory.quasi_measure_preserving`) and for every measurable set `s` of
positive measure at least one point `x ∈ s` returns back to `s` after some number of iterations of
`f`. There are several properties that look like they are stronger than this one but actually follow
from it:

* `measure_theory.conservative.frequently_measure_inter_ne_zero`,
  `measure_theory.conservative.exists_gt_measure_inter_ne_zero`: if `μ s ≠ 0`, then for infinitely
  many `n`, the measure of `s ∩ (f^[n]) ⁻¹' s` is positive.

* `measure_theory.conservative.measure_mem_forall_ge_image_not_mem_eq_zero`,
  `measure_theory.conservative.ae_mem_imp_frequently_image_mem`: a.e. every point of `s` visits `s`
  infinitely many times (Poincaré recurrence theorem).

We also prove the topological Poincaré recurrence theorem
`measure_theory.conservative.ae_frequently_mem_of_mem_nhds`. Let `f : α → α` be a conservative
dynamical system on a topological space with second countable topology and measurable open
sets. Then almost every point `x : α` is recurrent: it visits every neighborhood `s ∈ 𝓝 x`
infinitely many times.

## Tags

conservative dynamical system, Poincare recurrence theorem
-/

noncomputable theory

open classical set filter measure_theory finset function topological_space
open_locale classical topological_space

variables {ι : Type*} {α : Type*} [measurable_space α] {f : α → α} {s : set α} {μ : measure α}

namespace measure_theory

open measure

/-- We say that a non-singular (`measure_theory.quasi_measure_preserving`) self-map is
*conservative* if for any measurable set `s` of positive measure there exists `x ∈ s` such that `x`
returns back to `s` under some iteration of `f`. -/
structure conservative (f : α → α) (μ : measure α . volume_tac)
  extends quasi_measure_preserving f μ μ : Prop :=
(exists_mem_image_mem : ∀ ⦃s⦄, measurable_set s → μ s ≠ 0 → ∃ (x ∈ s) (m ≠ 0), f^[m] x ∈ s)

/-- A self-map preserving a finite measure is conservative. -/
protected lemma measure_preserving.conservative [finite_measure μ] (h : measure_preserving f μ μ) :
  conservative f μ :=
⟨h.quasi_measure_preserving, λ s hsm h0, h.exists_mem_image_mem hsm h0⟩

namespace conservative

/-- The identity map is conservative w.r.t. any measure. -/
protected lemma id (μ : measure α) : conservative id μ :=
{ to_quasi_measure_preserving := quasi_measure_preserving.id μ,
  exists_mem_image_mem := λ s hs h0, let ⟨x, hx⟩ := nonempty_of_measure_ne_zero h0 in
    ⟨x, hx, 1, one_ne_zero, hx⟩ }

/-- If `f` is a conservative map and `s` is a measurable set of nonzero measure, then
for infinitely many values of `m` a positive measure of points `x ∈ s` returns back to `s`
after `m` iterations of `f`. -/
lemma frequently_measure_inter_ne_zero (hf : conservative f μ) (hs : measurable_set s)
  (h0 : μ s ≠ 0) :
  ∃ᶠ m in at_top, μ (s ∩ (f^[m]) ⁻¹' s) ≠ 0 :=
begin
  by_contra H, simp only [not_frequently, eventually_at_top, ne.def, not_not] at H,
  rcases H with ⟨N, hN⟩,
  induction N with N ihN,
  { apply h0, simpa using hN 0 le_rfl },
  rw [imp_false] at ihN, push_neg at ihN,
  rcases ihN with ⟨n, hn, hμn⟩,
  set T := s ∩ ⋃ n ≥ N + 1, (f^[n]) ⁻¹' s,
  have hT : measurable_set T,
    from hs.inter (measurable_set.bUnion (countable_encodable _)
      (λ _ _, hf.measurable.iterate _ hs)),
  have hμT : μ T = 0,
  { convert (measure_bUnion_null_iff $ countable_encodable _).2 hN,
    rw ← set.inter_bUnion, refl },
  have : μ ((s ∩ (f^[n]) ⁻¹' s) \ T) ≠ 0, by rwa [measure_diff_null hμT],
  rcases hf.exists_mem_image_mem ((hs.inter (hf.measurable.iterate n hs)).diff hT) this
    with ⟨x, ⟨⟨hxs, hxn⟩, hxT⟩, m, hm0, ⟨hxms, hxm⟩, hxx⟩,
  refine hxT ⟨hxs, mem_bUnion_iff.2 ⟨n + m, _, _⟩⟩,
  { exact add_le_add hn (nat.one_le_of_lt $ pos_iff_ne_zero.2 hm0) },
  { rwa [set.mem_preimage, ← iterate_add_apply] at hxm }
end

/-- If `f` is a conservative map and `s` is a measurable set of nonzero measure, then
for an arbitrarily large `m` a positive measure of points `x ∈ s` returns back to `s`
after `m` iterations of `f`. -/
lemma exists_gt_measure_inter_ne_zero (hf : conservative f μ) (hs : measurable_set s) (h0 : μ s ≠ 0)
  (N : ℕ) :
  ∃ m > N, μ (s ∩ (f^[m]) ⁻¹' s) ≠ 0 :=
let ⟨m, hm, hmN⟩ :=
  ((hf.frequently_measure_inter_ne_zero hs h0).and_eventually (eventually_gt_at_top N)).exists
in ⟨m, hmN, hm⟩

/-- Poincaré recurrence theorem: given a conservative map `f` and a measurable set `s`, the set
of points `x ∈ s` such that `x` does not return to `s` after `≥ n` iterations has measure zero. -/
lemma measure_mem_forall_ge_image_not_mem_eq_zero (hf : conservative f μ) (hs : measurable_set s)
  (n : ℕ) :
  μ {x ∈ s | ∀ m ≥ n, f^[m] x ∉ s} = 0 :=
begin
  by_contradiction H,
  have : measurable_set (s ∩ {x | ∀ m ≥ n, f^[m] x ∉ s}),
  { simp only [set_of_forall, ← compl_set_of],
    exact hs.inter (measurable_set.bInter (countable_encodable _)
      (λ m _, hf.measurable.iterate m hs.compl)) },
  rcases (hf.exists_gt_measure_inter_ne_zero this H) n with ⟨m, hmn, hm⟩,
  rcases nonempty_of_measure_ne_zero hm with ⟨x, ⟨hxs, hxn⟩, hxm, -⟩,
  exact hxn m hmn.lt.le hxm
end

/-- Poincaré recurrence theorem: given a conservative map `f` and a measurable set `s`,
almost every point `x ∈ s` returns back to `s` infinitely many times. -/
lemma ae_mem_imp_frequently_image_mem (hf : conservative f μ) (hs : measurable_set s) :
  ∀ᵐ x ∂μ, x ∈ s → ∃ᶠ n in at_top, (f^[n] x) ∈ s :=
begin
  simp only [frequently_at_top, @forall_swap (_ ∈ s), ae_all_iff],
  intro n,
  filter_upwards [measure_zero_iff_ae_nmem.1 (hf.measure_mem_forall_ge_image_not_mem_eq_zero hs n)],
  simp
end

lemma inter_frequently_image_mem_ae_eq (hf : conservative f μ) (hs : measurable_set s) :
  (s ∩ {x | ∃ᶠ n in at_top, f^[n] x ∈ s} : set α) =ᵐ[μ] s :=
inter_eventually_eq_left.2 $ hf.ae_mem_imp_frequently_image_mem hs

/-- Poincaré recurrence theorem: if `f` is a conservative dynamical system and `s` is a measurable
set, then for `μ`-a.e. `x`, if the orbit of `x` visits `s` at least once, then it visits `s`
infinitely many times.  -/
lemma ae_forall_image_mem_imp_frequently_image_mem (hf : conservative f μ) (hs : measurable_set s) :
  ∀ᵐ x ∂μ, ∀ k, f^[k] x ∈ s → ∃ᶠ n in at_top, (f^[n] x) ∈ s :=
begin
  refine ae_all_iff.2 (λ k, _),
  refine (hf.ae_mem_imp_frequently_image_mem (hf.measurable.iterate k hs)).mono (λ x hx hk, _),
  rw [← map_add_at_top_eq_nat k, frequently_map],
  refine (hx hk).mono (λ n hn, _),
  rwa [add_comm, iterate_add_apply]
end

/-- If `f` is a conservative self-map and `s` is a measurable set of positive measure, then
`μ.ae`-frequently we have `x ∈ s` and `s` returns to `s` under infinitely many iterations of `f`. -/
lemma frequently_ae_mem_and_frequently_image_mem (hf : conservative f μ) (hs : measurable_set s)
  (h0 : μ s ≠ 0) :
  ∃ᵐ x ∂μ, x ∈ s ∧ ∃ᶠ n in at_top, (f^[n] x) ∈ s :=
((frequently_ae_mem_iff.2 h0).and_eventually (hf.ae_mem_imp_frequently_image_mem hs)).mono $ λ x hx,
  ⟨hx.1, hx.2 hx.1⟩

/-- Poincaré recurrence theorem. Let `f : α → α` be a conservative dynamical system on a topological
space with second countable topology and measurable open sets. Then almost every point `x : α`
is recurrent: it visits every neighborhood `s ∈ 𝓝 x` infinitely many times. -/
lemma ae_frequently_mem_of_mem_nhds [topological_space α] [second_countable_topology α]
  [opens_measurable_space α] {f : α → α} {μ : measure α} (h : conservative f μ) :
  ∀ᵐ x ∂μ, ∀ s ∈ 𝓝 x, ∃ᶠ n in at_top, f^[n] x ∈ s :=
begin
  have : ∀ s ∈ countable_basis α, ∀ᵐ x ∂μ, x ∈ s → ∃ᶠ n in at_top, (f^[n] x) ∈ s,
    from λ s hs, h.ae_mem_imp_frequently_image_mem
      (is_open_of_mem_countable_basis hs).measurable_set,
  refine ((ae_ball_iff $ countable_countable_basis α).2 this).mono (λ x hx s hs, _),
  rcases (is_basis_countable_basis α).mem_nhds_iff.1 hs with ⟨o, hoS, hxo, hos⟩,
  exact (hx o hoS hxo).mono (λ n hn, hos hn)
end

/-- Iteration of a conservative system is a conservative system. -/
protected lemma iterate (hf : conservative f μ) (n : ℕ) : conservative (f^[n]) μ :=
begin
  cases n, { exact conservative.id μ }, -- Discharge the trivial case `n = 0`
  refine ⟨hf.1.iterate _, λ s hs hs0, _⟩,
  rcases (hf.frequently_ae_mem_and_frequently_image_mem hs hs0).exists with ⟨x, hxs, hx⟩,
  /- We take a point `x ∈ s` such that `f^[k] x ∈ s` for infinitely many values of `k`,
  then we choose two of these values `k < l` such that `k ≡ l [MOD (n + 1)]`.
  Then `f^[k] x ∈ s` and `(f^[n + 1])^[(l - k) / (n + 1)] (f^[k] x) = f^[l] x ∈ s`. -/
  rw nat.frequently_at_top_iff_infinite at hx,
  rcases nat.exists_lt_modeq_of_infinite hx n.succ_pos with ⟨k, hk, l, hl, hkl, hn⟩,
  set m := (l - k) / (n + 1),
  have : (n + 1) * m = l - k,
  { apply nat.mul_div_cancel',
    exact (nat.modeq.modeq_iff_dvd' hkl.le).1 hn },
  refine ⟨f^[k] x, hk, m, _, _⟩,
  { intro hm,
    rw [hm, mul_zero, eq_comm, nat.sub_eq_zero_iff_le] at this,
    exact this.not_lt hkl },
  { rwa [← iterate_mul, this, ← iterate_add_apply, nat.sub_add_cancel],
    exact hkl.le }
end

end conservative

end measure_theory


/-
Copyright © 2020 Nicolò Cavalleri. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Nicolò Cavalleri, Yury Kudryashov
-/

import geometry.manifold.cont_mdiff_map

/-!
# Diffeomorphisms
This file implements diffeomorphisms.

## Definitions

* `diffeomorph I I' M M' n`:  `n`-times continuously differentiable diffeomorphism between
  `M` and `M'` with respect to I and I'; we do not introduce a separate definition for the case
  `n = ∞`; we use notation instead.
* `diffeomorph.to_homeomorph`: reinterpret a diffeomorphism as a homeomorphism.
* `continuous_linear_equiv.to_diffeomorph`: reinterpret a continuous equivalence as
  a diffeomorphism.
* `model_with_corners.trans_diffeomorph`: compose a given `model_with_corners` with a diffeomorphism
  between the old and the new target spaces. Useful, e.g, to turn any finite dimensional manifold
  into a manifold modelled on a Euclidean space.
* `diffeomorph.to_trans_diffeomorph`: the identity diffeomorphism between `M` with model `I` and `M`
  with model `I.trans_diffeomorph e`.

## Notations

* `M ≃ₘ^n⟮I, I'⟯ M'`  := `diffeomorph I J M N n`
* `M ≃ₘ⟮I, I'⟯ M'`    := `diffeomorph I J M N ⊤`
* `E ≃ₘ^n[𝕜] E'`      := `E ≃ₘ^n⟮𝓘(𝕜, E), 𝓘(𝕜, E')⟯ E'`
* `E ≃ₘ[𝕜] E'`        := `E ≃ₘ⟮𝓘(𝕜, E), 𝓘(𝕜, E')⟯ E'`

## Implementation notes

This notion of diffeomorphism is needed although there is already a notion of structomorphism
because structomorphisms do not allow the model spaces `H` and `H'` of the two manifolds to be
different, i.e. for a structomorphism one has to impose `H = H'` which is often not the case in
practice.

## Keywords

diffeomorphism, manifold
-/

open_locale manifold topological_space
open function set

variables {𝕜 : Type*} [nondiscrete_normed_field 𝕜]
{E : Type*} [normed_group E] [normed_space 𝕜 E]
{E' : Type*} [normed_group E'] [normed_space 𝕜 E']
{F : Type*} [normed_group F] [normed_space 𝕜 F]
{H : Type*} [topological_space H]
{H' : Type*} [topological_space H']
{G : Type*} [topological_space G]
{I : model_with_corners 𝕜 E H} {I' : model_with_corners 𝕜 E' H'}
{J : model_with_corners 𝕜 F G}

variables {M : Type*} [topological_space M] [charted_space H M]
{M' : Type*} [topological_space M'] [charted_space H' M']
{N : Type*} [topological_space N] [charted_space G N]
{n : with_top ℕ}

section defs

variables (I I' M M' n)

/--
`n`-times continuously differentiable diffeomorphism between `M` and `M'` with respect to I and I'
-/
@[protect_proj, nolint has_inhabited_instance]
structure diffeomorph extends M ≃ M' :=
(cont_mdiff_to_fun  : cont_mdiff I I' n to_equiv)
(cont_mdiff_inv_fun : cont_mdiff I' I n to_equiv.symm)

end defs

localized "notation M ` ≃ₘ^` n:1000 `⟮`:50 I `,` J `⟯ ` N := diffeomorph I J M N n" in manifold
localized "notation M ` ≃ₘ⟮` I `,` J `⟯ ` N := diffeomorph I J M N ⊤" in manifold
localized "notation E ` ≃ₘ^` n:1000 `[`:50 𝕜 `] ` E' :=
  diffeomorph (model_with_corners_self 𝕜 E) (model_with_corners_self 𝕜 E') E E' n" in manifold
localized "notation E ` ≃ₘ[` 𝕜 `] ` E' :=
  diffeomorph (model_with_corners_self 𝕜 E) (model_with_corners_self 𝕜 E') E E' ⊤" in manifold

namespace diffeomorph
instance : has_coe_to_fun (M ≃ₘ^n⟮I, I'⟯ M') (λ _, M → M') := ⟨λe, e.to_equiv⟩

instance : has_coe (M ≃ₘ^n⟮I, I'⟯ M') C^n⟮I, M; I', M'⟯ := ⟨λ Φ, ⟨Φ, Φ.cont_mdiff_to_fun⟩⟩

@[continuity] protected lemma continuous (h : M ≃ₘ^n⟮I, I'⟯ M') : continuous h :=
h.cont_mdiff_to_fun.continuous
protected lemma cont_mdiff (h : M ≃ₘ^n⟮I, I'⟯ M') : cont_mdiff I I' n h :=
h.cont_mdiff_to_fun
protected lemma cont_mdiff_at (h : M ≃ₘ^n⟮I, I'⟯ M') {x} : cont_mdiff_at I I' n h x :=
h.cont_mdiff.cont_mdiff_at
protected lemma cont_mdiff_within_at (h : M ≃ₘ^n⟮I, I'⟯ M') {s x} :
  cont_mdiff_within_at I I' n h s x :=
h.cont_mdiff_at.cont_mdiff_within_at
protected lemma cont_diff (h : E ≃ₘ^n[𝕜] E') : cont_diff 𝕜 n h :=
h.cont_mdiff.cont_diff
protected lemma smooth (h : M ≃ₘ⟮I, I'⟯ M') : smooth I I' h := h.cont_mdiff_to_fun
protected lemma mdifferentiable (h : M ≃ₘ^n⟮I, I'⟯ M') (hn : 1 ≤ n) : mdifferentiable I I' h :=
h.cont_mdiff.mdifferentiable hn
protected lemma mdifferentiable_on (h : M ≃ₘ^n⟮I, I'⟯ M') (s : set M) (hn : 1 ≤ n) :
  mdifferentiable_on I I' h s :=
(h.mdifferentiable hn).mdifferentiable_on

@[simp] lemma coe_to_equiv (h : M ≃ₘ^n⟮I, I'⟯ M') : ⇑h.to_equiv = h := rfl
@[simp, norm_cast] lemma coe_coe (h : M ≃ₘ^n⟮I, I'⟯ M') : ⇑(h : C^n⟮I, M; I', M'⟯) = h := rfl

lemma to_equiv_injective : injective (diffeomorph.to_equiv : (M ≃ₘ^n⟮I, I'⟯ M') → (M ≃ M'))
| ⟨e, _, _⟩ ⟨e', _, _⟩ rfl := rfl

@[simp] lemma to_equiv_inj {h h' : M ≃ₘ^n⟮I, I'⟯ M'} : h.to_equiv = h'.to_equiv ↔ h = h' :=
to_equiv_injective.eq_iff

/-- Coercion to function `λ h : M ≃ₘ^n⟮I, I'⟯ M', (h : M → M')` is injective. -/
lemma coe_fn_injective : injective (λ (h : M ≃ₘ^n⟮I, I'⟯ M') (x : M), h x) :=
equiv.coe_fn_injective.comp to_equiv_injective

@[ext] lemma ext {h h' : M ≃ₘ^n⟮I, I'⟯ M'} (Heq : ∀ x, h x = h' x) : h = h' :=
coe_fn_injective $ funext Heq

section

variables (M I n)

/-- Identity map as a diffeomorphism. -/
protected def refl : M ≃ₘ^n⟮I, I⟯ M :=
{ cont_mdiff_to_fun := cont_mdiff_id,
  cont_mdiff_inv_fun := cont_mdiff_id,
  to_equiv := equiv.refl M }

@[simp] lemma refl_to_equiv : (diffeomorph.refl I M n).to_equiv = equiv.refl _ := rfl
@[simp] lemma coe_refl : ⇑(diffeomorph.refl I M n) = id := rfl

end

/-- Composition of two diffeomorphisms. -/
protected def trans (h₁ : M ≃ₘ^n⟮I, I'⟯ M') (h₂ : M' ≃ₘ^n⟮I', J⟯ N) :
  M ≃ₘ^n⟮I, J⟯ N :=
{ cont_mdiff_to_fun  := h₂.cont_mdiff_to_fun.comp h₁.cont_mdiff_to_fun,
  cont_mdiff_inv_fun := h₁.cont_mdiff_inv_fun.comp h₂.cont_mdiff_inv_fun,
  to_equiv := h₁.to_equiv.trans h₂.to_equiv }

@[simp] lemma trans_refl (h : M ≃ₘ^n⟮I, I'⟯ M') : h.trans (diffeomorph.refl I' M' n) = h :=
ext $ λ _, rfl
@[simp] lemma refl_trans (h : M ≃ₘ^n⟮I, I'⟯ M') : (diffeomorph.refl I M n).trans h = h :=
ext $ λ _, rfl
@[simp] lemma coe_trans (h₁ : M ≃ₘ^n⟮I, I'⟯ M') (h₂ : M' ≃ₘ^n⟮I', J⟯ N) :
  ⇑(h₁.trans h₂) = h₂ ∘ h₁ := rfl

/-- Inverse of a diffeomorphism. -/
protected def symm (h : M ≃ₘ^n⟮I, J⟯ N) : N ≃ₘ^n⟮J, I⟯ M :=
{ cont_mdiff_to_fun  := h.cont_mdiff_inv_fun,
  cont_mdiff_inv_fun := h.cont_mdiff_to_fun,
  to_equiv := h.to_equiv.symm }

@[simp] lemma apply_symm_apply (h : M ≃ₘ^n⟮I, J⟯ N) (x : N) : h (h.symm x) = x :=
h.to_equiv.apply_symm_apply x
@[simp] lemma symm_apply_apply (h : M ≃ₘ^n⟮I, J⟯ N) (x : M) : h.symm (h x) = x :=
h.to_equiv.symm_apply_apply x

@[simp] lemma symm_refl : (diffeomorph.refl I M n).symm = diffeomorph.refl I M n :=
ext $ λ _, rfl
@[simp] lemma self_trans_symm (h : M ≃ₘ^n⟮I, J⟯ N) : h.trans h.symm = diffeomorph.refl I M n :=
ext h.symm_apply_apply
@[simp] lemma symm_trans_self (h : M ≃ₘ^n⟮I, J⟯ N) : h.symm.trans h = diffeomorph.refl J N n :=
ext h.apply_symm_apply
@[simp] lemma symm_trans' (h₁ : M ≃ₘ^n⟮I, I'⟯ M') (h₂ : M' ≃ₘ^n⟮I', J⟯ N) :
  (h₁.trans h₂).symm = h₂.symm.trans h₁.symm := rfl
@[simp] lemma symm_to_equiv (h : M ≃ₘ^n⟮I, J⟯ N) : h.symm.to_equiv = h.to_equiv.symm := rfl
@[simp, mfld_simps] lemma to_equiv_coe_symm (h : M ≃ₘ^n⟮I, J⟯ N) : ⇑h.to_equiv.symm = h.symm := rfl

lemma image_eq_preimage (h : M ≃ₘ^n⟮I, J⟯ N) (s : set M) : h '' s = h.symm ⁻¹' s :=
h.to_equiv.image_eq_preimage s
lemma symm_image_eq_preimage (h : M ≃ₘ^n⟮I, J⟯ N) (s : set N) : h.symm '' s = h ⁻¹' s :=
h.symm.image_eq_preimage s

@[simp, mfld_simps] lemma range_comp {α} (h : M ≃ₘ^n⟮I, J⟯ N) (f : α → M) :
  range (h ∘ f) = h.symm ⁻¹' (range f) :=
by rw [range_comp, image_eq_preimage]

@[simp] lemma image_symm_image (h : M ≃ₘ^n⟮I, J⟯ N) (s : set N) : h '' (h.symm '' s) = s :=
h.to_equiv.image_symm_image s
@[simp] lemma symm_image_image (h : M ≃ₘ^n⟮I, J⟯ N) (s : set M) : h.symm '' (h '' s) = s :=
h.to_equiv.symm_image_image s

/-- A diffeomorphism is a homeomorphism. -/
def to_homeomorph (h : M ≃ₘ^n⟮I, J⟯ N) : M ≃ₜ N :=
⟨h.to_equiv, h.continuous, h.symm.continuous⟩

@[simp] lemma to_homeomorph_to_equiv (h : M ≃ₘ^n⟮I, J⟯ N) :
  h.to_homeomorph.to_equiv = h.to_equiv :=
rfl
@[simp] lemma symm_to_homeomorph (h : M ≃ₘ^n⟮I, J⟯ N) :
  h.symm.to_homeomorph = h.to_homeomorph.symm :=
rfl

@[simp] lemma coe_to_homeomorph (h : M ≃ₘ^n⟮I, J⟯ N) : ⇑h.to_homeomorph = h := rfl
@[simp] lemma coe_to_homeomorph_symm (h : M ≃ₘ^n⟮I, J⟯ N) :
  ⇑h.to_homeomorph.symm = h.symm := rfl

@[simp] lemma cont_mdiff_within_at_comp_diffeomorph_iff {m} (h : M ≃ₘ^n⟮I, J⟯ N) {f : N → M'}
  {s x} (hm : m ≤ n) :
  cont_mdiff_within_at I I' m (f ∘ h) s x ↔
    cont_mdiff_within_at J I' m f (h.symm ⁻¹' s) (h x) :=
begin
  split,
  { intro Hfh,
    rw [← h.symm_apply_apply x] at Hfh,
    simpa only [(∘), h.apply_symm_apply]
      using Hfh.comp (h x) (h.symm.cont_mdiff_within_at.of_le hm) (maps_to_preimage _ _) },
  { rw ← h.image_eq_preimage,
    exact λ hf, hf.comp x (h.cont_mdiff_within_at.of_le hm) (maps_to_image _ _) }
end

@[simp] lemma cont_mdiff_on_comp_diffeomorph_iff {m} (h : M ≃ₘ^n⟮I, J⟯ N) {f : N → M'}
  {s} (hm : m ≤ n) :
  cont_mdiff_on I I' m (f ∘ h) s ↔ cont_mdiff_on J I' m f (h.symm ⁻¹' s) :=
h.to_equiv.forall_congr $ λ x, by simp only [hm, coe_to_equiv, symm_apply_apply,
  cont_mdiff_within_at_comp_diffeomorph_iff, mem_preimage]

@[simp] lemma cont_mdiff_at_comp_diffeomorph_iff {m} (h : M ≃ₘ^n⟮I, J⟯ N) {f : N → M'} {x}
  (hm : m ≤ n) :
  cont_mdiff_at I I' m (f ∘ h) x ↔ cont_mdiff_at J I' m f (h x) :=
h.cont_mdiff_within_at_comp_diffeomorph_iff hm

@[simp] lemma cont_mdiff_comp_diffeomorph_iff {m} (h : M ≃ₘ^n⟮I, J⟯ N) {f : N → M'}
  (hm : m ≤ n) :
  cont_mdiff I I' m (f ∘ h) ↔ cont_mdiff J I' m f :=
h.to_equiv.forall_congr $ λ x, (h.cont_mdiff_at_comp_diffeomorph_iff hm)

@[simp] lemma cont_mdiff_within_at_diffeomorph_comp_iff {m} (h : M ≃ₘ^n⟮I, J⟯ N) {f : M' → M}
  (hm : m ≤ n) {s x} :
  cont_mdiff_within_at I' J m (h ∘ f) s x ↔ cont_mdiff_within_at I' I m f s x :=
⟨λ Hhf, by simpa only [(∘), h.symm_apply_apply]
  using (h.symm.cont_mdiff_at.of_le hm).comp_cont_mdiff_within_at _ Hhf,
  λ Hf, (h.cont_mdiff_at.of_le hm).comp_cont_mdiff_within_at _ Hf⟩

@[simp] lemma cont_mdiff_at_diffeomorph_comp_iff {m} (h : M ≃ₘ^n⟮I, J⟯ N) {f : M' → M}
  (hm : m ≤ n) {x} :
  cont_mdiff_at I' J m (h ∘ f) x ↔ cont_mdiff_at I' I m f x :=
h.cont_mdiff_within_at_diffeomorph_comp_iff hm

@[simp] lemma cont_mdiff_on_diffeomorph_comp_iff {m} (h : M ≃ₘ^n⟮I, J⟯ N) {f : M' → M}
  (hm : m ≤ n) {s} :
  cont_mdiff_on I' J m (h ∘ f) s ↔ cont_mdiff_on I' I m f s :=
forall₂_congr $ λ x hx, h.cont_mdiff_within_at_diffeomorph_comp_iff hm

@[simp] lemma cont_mdiff_diffeomorph_comp_iff {m} (h : M ≃ₘ^n⟮I, J⟯ N) {f : M' → M}
  (hm : m ≤ n) :
  cont_mdiff I' J m (h ∘ f) ↔ cont_mdiff I' I m f :=
forall_congr $ λ x, h.cont_mdiff_within_at_diffeomorph_comp_iff hm

lemma to_local_homeomorph_mdifferentiable (h : M ≃ₘ^n⟮I, J⟯ N) (hn : 1 ≤ n) :
  h.to_homeomorph.to_local_homeomorph.mdifferentiable I J :=
⟨h.mdifferentiable_on _ hn, h.symm.mdifferentiable_on _ hn⟩

variables [smooth_manifold_with_corners I M] [smooth_manifold_with_corners J N]

lemma unique_mdiff_on_image_aux (h : M ≃ₘ^n⟮I, J⟯ N) (hn : 1 ≤ n)
  {s : set M} (hs : unique_mdiff_on I s) :
  unique_mdiff_on J (h '' s) :=
begin
  convert hs.unique_mdiff_on_preimage (h.to_local_homeomorph_mdifferentiable hn),
  simp [h.image_eq_preimage]
end

@[simp] lemma unique_mdiff_on_image (h : M ≃ₘ^n⟮I, J⟯ N) (hn : 1 ≤ n) {s : set M} :
  unique_mdiff_on J (h '' s) ↔ unique_mdiff_on I s :=
⟨λ hs, h.symm_image_image s ▸ h.symm.unique_mdiff_on_image_aux hn hs,
  h.unique_mdiff_on_image_aux hn⟩

@[simp] lemma unique_mdiff_on_preimage (h : M ≃ₘ^n⟮I, J⟯ N) (hn : 1 ≤ n) {s : set N} :
  unique_mdiff_on I (h ⁻¹' s) ↔ unique_mdiff_on J s :=
h.symm_image_eq_preimage s ▸ h.symm.unique_mdiff_on_image hn

@[simp] lemma unique_diff_on_image (h : E ≃ₘ^n[𝕜] F) (hn : 1 ≤ n) {s : set E} :
  unique_diff_on 𝕜 (h '' s) ↔ unique_diff_on 𝕜 s :=
by simp only [← unique_mdiff_on_iff_unique_diff_on, unique_mdiff_on_image, hn]

@[simp] lemma unique_diff_on_preimage (h : E ≃ₘ^n[𝕜] F) (hn : 1 ≤ n) {s : set F} :
  unique_diff_on 𝕜 (h ⁻¹' s) ↔ unique_diff_on 𝕜 s :=
h.symm_image_eq_preimage s ▸ h.symm.unique_diff_on_image hn

end diffeomorph

namespace continuous_linear_equiv

variable (e : E ≃L[𝕜] E')

/-- A continuous linear equivalence between normed spaces is a diffeomorphism. -/
def to_diffeomorph : E ≃ₘ[𝕜] E' :=
{ cont_mdiff_to_fun := e.cont_diff.cont_mdiff,
  cont_mdiff_inv_fun := e.symm.cont_diff.cont_mdiff,
  to_equiv := e.to_linear_equiv.to_equiv }

@[simp] lemma coe_to_diffeomorph : ⇑e.to_diffeomorph = e := rfl
@[simp] lemma symm_to_diffeomorph : e.symm.to_diffeomorph = e.to_diffeomorph.symm := rfl
@[simp] lemma coe_to_diffeomorph_symm : ⇑e.to_diffeomorph.symm = e.symm := rfl

end continuous_linear_equiv

namespace model_with_corners

variables (I) (e : E ≃ₘ[𝕜] E')

/-- Apply a diffeomorphism (e.g., a continuous linear equivalence) to the model vector space. -/
def trans_diffeomorph (I : model_with_corners 𝕜 E H) (e : E ≃ₘ[𝕜] E') :
  model_with_corners 𝕜 E' H :=
{ to_local_equiv := I.to_local_equiv.trans e.to_equiv.to_local_equiv,
  source_eq := by simp,
  unique_diff' := by simp [range_comp e, I.unique_diff],
  continuous_to_fun := e.continuous.comp I.continuous,
  continuous_inv_fun := I.continuous_symm.comp e.symm.continuous }

@[simp, mfld_simps] lemma coe_trans_diffeomorph : ⇑(I.trans_diffeomorph e) = e ∘ I := rfl
@[simp, mfld_simps] lemma coe_trans_diffeomorph_symm :
  ⇑(I.trans_diffeomorph e).symm = I.symm ∘ e.symm := rfl

lemma trans_diffeomorph_range : range (I.trans_diffeomorph e) = e '' (range I) :=
range_comp e I

lemma coe_ext_chart_at_trans_diffeomorph (x : M) :
  ⇑(ext_chart_at (I.trans_diffeomorph e) x) = e ∘ ext_chart_at I x := rfl

lemma coe_ext_chart_at_trans_diffeomorph_symm (x : M) :
  ⇑(ext_chart_at (I.trans_diffeomorph e) x).symm = (ext_chart_at I x).symm ∘ e.symm := rfl

lemma ext_chart_at_trans_diffeomorph_target (x : M) :
  (ext_chart_at (I.trans_diffeomorph e) x).target = e.symm ⁻¹' (ext_chart_at I x).target :=
by simp only [range_comp e, e.image_eq_preimage, preimage_preimage] with mfld_simps

end model_with_corners

namespace diffeomorph

variables (e : E ≃ₘ[𝕜] F)

instance smooth_manifold_with_corners_trans_diffeomorph [smooth_manifold_with_corners I M] :
  smooth_manifold_with_corners (I.trans_diffeomorph e) M :=
begin
  refine smooth_manifold_with_corners_of_cont_diff_on  _ _ (λ e₁ e₂ h₁ h₂, _),
  refine e.cont_diff.comp_cont_diff_on
    (((cont_diff_groupoid ⊤ I).compatible h₁ h₂).1.comp
      e.symm.cont_diff.cont_diff_on _),
  mfld_set_tac
end

variables (I M)

/-- The identity diffeomorphism between a manifold with model `I` and the same manifold
with model `I.trans_diffeomorph e`. -/
def to_trans_diffeomorph (e : E ≃ₘ[𝕜] F) : M ≃ₘ⟮I, I.trans_diffeomorph e⟯ M :=
{ to_equiv := equiv.refl M,
  cont_mdiff_to_fun := λ x,
    begin
      refine cont_mdiff_within_at_iff.2 ⟨continuous_within_at_id, _⟩,
      refine e.cont_diff.cont_diff_within_at.congr' (λ y hy, _) _,
      { simp only [equiv.coe_refl, id, (∘), I.coe_ext_chart_at_trans_diffeomorph,
          (ext_chart_at I x).right_inv hy.1] },
      exact ⟨(ext_chart_at I x).map_source (mem_ext_chart_source I x), trivial,
        by simp only with mfld_simps⟩
    end,
  cont_mdiff_inv_fun := λ x,
    begin
      refine cont_mdiff_within_at_iff.2 ⟨continuous_within_at_id, _⟩,
      refine e.symm.cont_diff.cont_diff_within_at.congr' (λ y hy, _) _,
      { simp only [mem_inter_eq, I.ext_chart_at_trans_diffeomorph_target] at hy,
        simp only [equiv.coe_refl, equiv.refl_symm, id, (∘),
          I.coe_ext_chart_at_trans_diffeomorph_symm, (ext_chart_at I x).right_inv hy.1] },
      exact ⟨(ext_chart_at _ x).map_source (mem_ext_chart_source _ x), trivial,
        by simp only [e.symm_apply_apply, equiv.refl_symm, equiv.coe_refl] with mfld_simps⟩
    end }

variables {I M}

@[simp] lemma cont_mdiff_within_at_trans_diffeomorph_right {f : M' → M} {x s} :
  cont_mdiff_within_at I' (I.trans_diffeomorph e) n f s x ↔
    cont_mdiff_within_at I' I n f s x :=
(to_trans_diffeomorph I M e).cont_mdiff_within_at_diffeomorph_comp_iff le_top

@[simp] lemma cont_mdiff_at_trans_diffeomorph_right {f : M' → M} {x} :
  cont_mdiff_at I' (I.trans_diffeomorph e) n f x ↔ cont_mdiff_at I' I n f x :=
(to_trans_diffeomorph I M e).cont_mdiff_at_diffeomorph_comp_iff le_top

@[simp] lemma cont_mdiff_on_trans_diffeomorph_right {f : M' → M} {s} :
  cont_mdiff_on I' (I.trans_diffeomorph e) n f s ↔ cont_mdiff_on I' I n f s :=
(to_trans_diffeomorph I M e).cont_mdiff_on_diffeomorph_comp_iff le_top

@[simp] lemma cont_mdiff_trans_diffeomorph_right {f : M' → M} :
  cont_mdiff I' (I.trans_diffeomorph e) n f ↔ cont_mdiff I' I n f :=
(to_trans_diffeomorph I M e).cont_mdiff_diffeomorph_comp_iff le_top

@[simp] lemma smooth_trans_diffeomorph_right {f : M' → M} :
  smooth I' (I.trans_diffeomorph e) f ↔ smooth I' I f :=
cont_mdiff_trans_diffeomorph_right e

@[simp]

@[simp] lemma cont_mdiff_at_trans_diffeomorph_left {f : M → M'} {x} :
  cont_mdiff_at (I.trans_diffeomorph e) I' n f x ↔ cont_mdiff_at I I' n f x :=
((to_trans_diffeomorph I M e).cont_mdiff_at_comp_diffeomorph_iff le_top).symm

@[simp] lemma cont_mdiff_on_trans_diffeomorph_left {f : M → M'} {s} :
  cont_mdiff_on (I.trans_diffeomorph e) I' n f s ↔ cont_mdiff_on I I' n f s :=
((to_trans_diffeomorph I M e).cont_mdiff_on_comp_diffeomorph_iff le_top).symm

@[simp] lemma cont_mdiff_trans_diffeomorph_left {f : M → M'} :
  cont_mdiff (I.trans_diffeomorph e) I' n f ↔ cont_mdiff I I' n f :=
((to_trans_diffeomorph I M e).cont_mdiff_comp_diffeomorph_iff le_top).symm

@[simp] lemma smooth_trans_diffeomorph_left {f : M → M'} :
  smooth (I.trans_diffeomorph e) I' f ↔ smooth I I' f :=
e.cont_mdiff_trans_diffeomorph_left

end diffeomorph