/-
Copyright (c) 2020 Joseph Myers. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joseph Myers, Manuel Candales

! This file was ported from Lean 3 source module geometry.euclidean.angle.unoriented.affine
! leanprover-community/mathlib commit 46b633fd842bef9469441c0209906f6dddd2b4f5
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.Convex.Between
import Mathbin.Geometry.Euclidean.Angle.Unoriented.Basic

/-!
# Angles between points

This file defines unoriented angles in Euclidean affine spaces.

## Main definitions

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

-/

noncomputable section

open BigOperators

open Real

open RealInnerProductSpace

namespace EuclideanGeometry

open InnerProductGeometry

variable {V : Type _} {P : Type _} [NormedAddCommGroup V] [InnerProductSpace ℝ V] [MetricSpace P]
  [NormedAddTorsor V P]

include V

/-- 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 (p1 p2 p3 : P) : ℝ :=
  angle (p1 -ᵥ p2 : V) (p3 -ᵥ p2)
#align euclidean_geometry.angle EuclideanGeometry.angle

-- mathport name: angle
scoped notation "∠" => EuclideanGeometry.angle

theorem continuousAt_angle {x : P × P × P} (hx12 : x.1 ≠ x.2.1) (hx32 : x.2.2 ≠ x.2.1) :
    ContinuousAt (fun y : P × P × P => ∠ y.1 y.2.1 y.2.2) x :=
  by
  let f : P × P × P → V × V := fun y => (y.1 -ᵥ y.2.1, y.2.2 -ᵥ y.2.1)
  have hf1 : (f x).1 ≠ 0 := by simp [hx12]
  have hf2 : (f x).2 ≠ 0 := by simp [hx32]
  exact
    (InnerProductGeometry.continuousAt_angle hf1 hf2).comp
      ((continuous_fst.vsub continuous_snd.fst).prod_mk
          (continuous_snd.snd.vsub continuous_snd.fst)).ContinuousAt
#align euclidean_geometry.continuous_at_angle EuclideanGeometry.continuousAt_angle

@[simp]
theorem AffineIsometry.angle_map {V₂ P₂ : Type _} [NormedAddCommGroup V₂] [InnerProductSpace ℝ V₂]
    [MetricSpace P₂] [NormedAddTorsor V₂ P₂] (f : P →ᵃⁱ[ℝ] P₂) (p₁ p₂ p₃ : P) :
    ∠ (f p₁) (f p₂) (f p₃) = ∠ p₁ p₂ p₃ := by
  simp_rw [angle, ← AffineIsometry.map_vsub, LinearIsometry.angle_map]
#align affine_isometry.angle_map AffineIsometry.angle_map

@[simp, norm_cast]
theorem AffineSubspace.angle_coe {s : AffineSubspace ℝ P} (p₁ p₂ p₃ : s) :
    haveI : Nonempty s := ⟨p₁⟩
    ∠ (p₁ : P) (p₂ : P) (p₃ : P) = ∠ p₁ p₂ p₃ :=
  haveI : Nonempty s := ⟨p₁⟩
  s.subtypeₐᵢ.angle_map p₁ p₂ p₃
#align affine_subspace.angle_coe AffineSubspace.angle_coe

/-- Angles are translation invariant -/
@[simp]
theorem angle_const_vadd (v : V) (p₁ p₂ p₃ : P) : ∠ (v +ᵥ p₁) (v +ᵥ p₂) (v +ᵥ p₃) = ∠ p₁ p₂ p₃ :=
  (AffineIsometryEquiv.constVadd ℝ P v).toAffineIsometry.angle_map _ _ _
#align euclidean_geometry.angle_const_vadd EuclideanGeometry.angle_const_vadd

/-- Angles are translation invariant -/
@[simp]
theorem angle_vadd_const (v₁ v₂ v₃ : V) (p : P) : ∠ (v₁ +ᵥ p) (v₂ +ᵥ p) (v₃ +ᵥ p) = ∠ v₁ v₂ v₃ :=
  (AffineIsometryEquiv.vaddConst ℝ p).toAffineIsometry.angle_map _ _ _
#align euclidean_geometry.angle_vadd_const EuclideanGeometry.angle_vadd_const

/-- Angles are translation invariant -/
@[simp]
theorem angle_const_vsub (p p₁ p₂ p₃ : P) : ∠ (p -ᵥ p₁) (p -ᵥ p₂) (p -ᵥ p₃) = ∠ p₁ p₂ p₃ :=
  (AffineIsometryEquiv.constVsub ℝ p).toAffineIsometry.angle_map _ _ _
#align euclidean_geometry.angle_const_vsub EuclideanGeometry.angle_const_vsub

/-- Angles are translation invariant -/
@[simp]
theorem angle_vsub_const (p₁ p₂ p₃ p : P) : ∠ (p₁ -ᵥ p) (p₂ -ᵥ p) (p₃ -ᵥ p) = ∠ p₁ p₂ p₃ :=
  (AffineIsometryEquiv.vaddConst ℝ p).symm.toAffineIsometry.angle_map _ _ _
#align euclidean_geometry.angle_vsub_const EuclideanGeometry.angle_vsub_const

/-- Angles in a vector space are translation invariant -/
@[simp]
theorem angle_add_const (v₁ v₂ v₃ : V) (v : V) : ∠ (v₁ + v) (v₂ + v) (v₃ + v) = ∠ v₁ v₂ v₃ :=
  angle_vadd_const _ _ _ _
#align euclidean_geometry.angle_add_const EuclideanGeometry.angle_add_const

/-- Angles in a vector space are translation invariant -/
@[simp]
theorem angle_const_add (v : V) (v₁ v₂ v₃ : V) : ∠ (v + v₁) (v + v₂) (v + v₃) = ∠ v₁ v₂ v₃ :=
  angle_const_vadd _ _ _ _
#align euclidean_geometry.angle_const_add EuclideanGeometry.angle_const_add

/-- Angles in a vector space are translation invariant -/
@[simp]
theorem angle_sub_const (v₁ v₂ v₃ : V) (v : V) : ∠ (v₁ - v) (v₂ - v) (v₃ - v) = ∠ v₁ v₂ v₃ := by
  simpa only [vsub_eq_sub] using angle_vsub_const v₁ v₂ v₃ v
#align euclidean_geometry.angle_sub_const EuclideanGeometry.angle_sub_const

/-- Angles in a vector space are invariant to inversion -/
@[simp]
theorem angle_const_sub (v : V) (v₁ v₂ v₃ : V) : ∠ (v - v₁) (v - v₂) (v - v₃) = ∠ v₁ v₂ v₃ := by
  simpa only [vsub_eq_sub] using angle_const_vsub _ _ _ _
#align euclidean_geometry.angle_const_sub EuclideanGeometry.angle_const_sub

/-- Angles in a vector space are invariant to inversion -/
@[simp]
theorem angle_neg (v₁ v₂ v₃ : V) : ∠ (-v₁) (-v₂) (-v₃) = ∠ v₁ v₂ v₃ := by
  simpa only [zero_sub] using angle_const_sub 0 v₁ v₂ v₃
#align euclidean_geometry.angle_neg EuclideanGeometry.angle_neg

/-- The angle at a point does not depend on the order of the other two
points. -/
theorem angle_comm (p1 p2 p3 : P) : ∠ p1 p2 p3 = ∠ p3 p2 p1 :=
  angle_comm _ _
#align euclidean_geometry.angle_comm EuclideanGeometry.angle_comm

/-- The angle at a point is nonnegative. -/
theorem angle_nonneg (p1 p2 p3 : P) : 0 ≤ ∠ p1 p2 p3 :=
  angle_nonneg _ _
#align euclidean_geometry.angle_nonneg EuclideanGeometry.angle_nonneg

/-- The angle at a point is at most π. -/
theorem angle_le_pi (p1 p2 p3 : P) : ∠ p1 p2 p3 ≤ π :=
  angle_le_pi _ _
#align euclidean_geometry.angle_le_pi EuclideanGeometry.angle_le_pi

/-- The angle ∠AAB at a point. -/
theorem angle_eq_left (p1 p2 : P) : ∠ p1 p1 p2 = π / 2 :=
  by
  unfold angle
  rw [vsub_self]
  exact angle_zero_left _
#align euclidean_geometry.angle_eq_left EuclideanGeometry.angle_eq_left

/-- The angle ∠ABB at a point. -/
theorem angle_eq_right (p1 p2 : P) : ∠ p1 p2 p2 = π / 2 := by rw [angle_comm, angle_eq_left]
#align euclidean_geometry.angle_eq_right EuclideanGeometry.angle_eq_right

/-- The angle ∠ABA at a point. -/
theorem angle_eq_of_ne {p1 p2 : P} (h : p1 ≠ p2) : ∠ p1 p2 p1 = 0 :=
  angle_self fun he => h (vsub_eq_zero_iff_eq.1 he)
#align euclidean_geometry.angle_eq_of_ne EuclideanGeometry.angle_eq_of_ne

/-- If the angle ∠ABC at a point is π, the angle ∠BAC is 0. -/
theorem angle_eq_zero_of_angle_eq_pi_left {p1 p2 p3 : P} (h : ∠ p1 p2 p3 = π) : ∠ p2 p1 p3 = 0 :=
  by
  unfold angle at h
  rw [angle_eq_pi_iff] at h
  rcases h with ⟨hp1p2, ⟨r, ⟨hr, hpr⟩⟩⟩
  unfold angle
  rw [angle_eq_zero_iff]
  rw [← neg_vsub_eq_vsub_rev, neg_ne_zero] at hp1p2
  use hp1p2, -r + 1, add_pos (neg_pos_of_neg hr) zero_lt_one
  rw [add_smul, ← neg_vsub_eq_vsub_rev p1 p2, smul_neg]
  simp [← hpr]
#align euclidean_geometry.angle_eq_zero_of_angle_eq_pi_left EuclideanGeometry.angle_eq_zero_of_angle_eq_pi_left

/-- If the angle ∠ABC at a point is π, the angle ∠BCA is 0. -/
theorem angle_eq_zero_of_angle_eq_pi_right {p1 p2 p3 : P} (h : ∠ p1 p2 p3 = π) : ∠ p2 p3 p1 = 0 :=
  by
  rw [angle_comm] at h
  exact angle_eq_zero_of_angle_eq_pi_left h
#align euclidean_geometry.angle_eq_zero_of_angle_eq_pi_right EuclideanGeometry.angle_eq_zero_of_angle_eq_pi_right

/-- If ∠BCD = π, then ∠ABC = ∠ABD. -/
theorem angle_eq_angle_of_angle_eq_pi (p1 : P) {p2 p3 p4 : P} (h : ∠ p2 p3 p4 = π) :
    ∠ p1 p2 p3 = ∠ p1 p2 p4 := by
  unfold angle at *
  rcases angle_eq_pi_iff.1 h with ⟨hp2p3, ⟨r, ⟨hr, hpr⟩⟩⟩
  rw [eq_comm]
  convert angle_smul_right_of_pos (p1 -ᵥ p2) (p3 -ᵥ p2) (add_pos (neg_pos_of_neg hr) zero_lt_one)
  rw [add_smul, ← neg_vsub_eq_vsub_rev p2 p3, smul_neg, neg_smul, ← hpr]
  simp
#align euclidean_geometry.angle_eq_angle_of_angle_eq_pi EuclideanGeometry.angle_eq_angle_of_angle_eq_pi

/-- If ∠BCD = π, then ∠ACB + ∠ACD = π. -/
theorem angle_add_angle_eq_pi_of_angle_eq_pi (p1 : P) {p2 p3 p4 : P} (h : ∠ p2 p3 p4 = π) :
    ∠ p1 p3 p2 + ∠ p1 p3 p4 = π := by
  unfold angle at h
  rw [angle_comm p1 p3 p2, angle_comm p1 p3 p4]
  unfold angle
  exact angle_add_angle_eq_pi_of_angle_eq_pi _ h
#align euclidean_geometry.angle_add_angle_eq_pi_of_angle_eq_pi EuclideanGeometry.angle_add_angle_eq_pi_of_angle_eq_pi

/-- Vertical Angles Theorem: angles opposite each other, formed by two intersecting straight
lines, are equal. -/
theorem angle_eq_angle_of_angle_eq_pi_of_angle_eq_pi {p1 p2 p3 p4 p5 : P} (hapc : ∠ p1 p5 p3 = π)
    (hbpd : ∠ p2 p5 p4 = π) : ∠ p1 p5 p2 = ∠ p3 p5 p4 := by
  linarith [angle_add_angle_eq_pi_of_angle_eq_pi p1 hbpd, angle_comm p4 p5 p1,
    angle_add_angle_eq_pi_of_angle_eq_pi p4 hapc, angle_comm p4 p5 p3]
#align euclidean_geometry.angle_eq_angle_of_angle_eq_pi_of_angle_eq_pi EuclideanGeometry.angle_eq_angle_of_angle_eq_pi_of_angle_eq_pi

/-- If ∠ABC = π then dist A B ≠ 0. -/
theorem left_dist_ne_zero_of_angle_eq_pi {p1 p2 p3 : P} (h : ∠ p1 p2 p3 = π) : dist p1 p2 ≠ 0 :=
  by
  by_contra heq
  rw [dist_eq_zero] at heq
  rw [HEq, angle_eq_left] at h
  exact Real.pi_ne_zero (by linarith)
#align euclidean_geometry.left_dist_ne_zero_of_angle_eq_pi EuclideanGeometry.left_dist_ne_zero_of_angle_eq_pi

/-- If ∠ABC = π then dist C B ≠ 0. -/
theorem right_dist_ne_zero_of_angle_eq_pi {p1 p2 p3 : P} (h : ∠ p1 p2 p3 = π) : dist p3 p2 ≠ 0 :=
  left_dist_ne_zero_of_angle_eq_pi <| (angle_comm _ _ _).trans h
#align euclidean_geometry.right_dist_ne_zero_of_angle_eq_pi EuclideanGeometry.right_dist_ne_zero_of_angle_eq_pi

/-- If ∠ABC = π, then (dist A C) = (dist A B) + (dist B C). -/
theorem dist_eq_add_dist_of_angle_eq_pi {p1 p2 p3 : P} (h : ∠ p1 p2 p3 = π) :
    dist p1 p3 = dist p1 p2 + dist p3 p2 :=
  by
  rw [dist_eq_norm_vsub V, dist_eq_norm_vsub V, dist_eq_norm_vsub V, ← vsub_sub_vsub_cancel_right]
  exact norm_sub_eq_add_norm_of_angle_eq_pi h
#align euclidean_geometry.dist_eq_add_dist_of_angle_eq_pi EuclideanGeometry.dist_eq_add_dist_of_angle_eq_pi

/-- If A ≠ B and C ≠ B then ∠ABC = π if and only if (dist A C) = (dist A B) + (dist B C). -/
theorem dist_eq_add_dist_iff_angle_eq_pi {p1 p2 p3 : P} (hp1p2 : p1 ≠ p2) (hp3p2 : p3 ≠ p2) :
    dist p1 p3 = dist p1 p2 + dist p3 p2 ↔ ∠ p1 p2 p3 = π :=
  by
  rw [dist_eq_norm_vsub V, dist_eq_norm_vsub V, dist_eq_norm_vsub V, ← vsub_sub_vsub_cancel_right]
  exact
    norm_sub_eq_add_norm_iff_angle_eq_pi (fun he => hp1p2 (vsub_eq_zero_iff_eq.1 he)) fun he =>
      hp3p2 (vsub_eq_zero_iff_eq.1 he)
#align euclidean_geometry.dist_eq_add_dist_iff_angle_eq_pi EuclideanGeometry.dist_eq_add_dist_iff_angle_eq_pi

/-- If ∠ABC = 0, then (dist A C) = abs ((dist A B) - (dist B C)). -/
theorem dist_eq_abs_sub_dist_of_angle_eq_zero {p1 p2 p3 : P} (h : ∠ p1 p2 p3 = 0) :
    dist p1 p3 = |dist p1 p2 - dist p3 p2| :=
  by
  rw [dist_eq_norm_vsub V, dist_eq_norm_vsub V, dist_eq_norm_vsub V, ← vsub_sub_vsub_cancel_right]
  exact norm_sub_eq_abs_sub_norm_of_angle_eq_zero h
#align euclidean_geometry.dist_eq_abs_sub_dist_of_angle_eq_zero EuclideanGeometry.dist_eq_abs_sub_dist_of_angle_eq_zero

/-- If A ≠ B and C ≠ B then ∠ABC = 0 if and only if (dist A C) = abs ((dist A B) - (dist B C)). -/
theorem dist_eq_abs_sub_dist_iff_angle_eq_zero {p1 p2 p3 : P} (hp1p2 : p1 ≠ p2) (hp3p2 : p3 ≠ p2) :
    dist p1 p3 = |dist p1 p2 - dist p3 p2| ↔ ∠ p1 p2 p3 = 0 :=
  by
  rw [dist_eq_norm_vsub V, dist_eq_norm_vsub V, dist_eq_norm_vsub V, ← vsub_sub_vsub_cancel_right]
  exact
    norm_sub_eq_abs_sub_norm_iff_angle_eq_zero (fun he => hp1p2 (vsub_eq_zero_iff_eq.1 he))
      fun he => hp3p2 (vsub_eq_zero_iff_eq.1 he)
#align euclidean_geometry.dist_eq_abs_sub_dist_iff_angle_eq_zero EuclideanGeometry.dist_eq_abs_sub_dist_iff_angle_eq_zero

/-- If M is the midpoint of the segment AB, then ∠AMB = π. -/
theorem angle_midpoint_eq_pi (p1 p2 : P) (hp1p2 : p1 ≠ p2) : ∠ p1 (midpoint ℝ p1 p2) p2 = π :=
  by
  have : p2 -ᵥ midpoint ℝ p1 p2 = -(p1 -ᵥ midpoint ℝ p1 p2) :=
    by
    rw [neg_vsub_eq_vsub_rev]
    simp
  simp [angle, this, hp1p2, -zero_lt_one]
#align euclidean_geometry.angle_midpoint_eq_pi EuclideanGeometry.angle_midpoint_eq_pi

/-- If M is the midpoint of the segment AB and C is the same distance from A as it is from B
then ∠CMA = π / 2. -/
theorem angle_left_midpoint_eq_pi_div_two_of_dist_eq {p1 p2 p3 : P} (h : dist p3 p1 = dist p3 p2) :
    ∠ p3 (midpoint ℝ p1 p2) p1 = π / 2 :=
  by
  let m : P := midpoint ℝ p1 p2
  have h1 : p3 -ᵥ p1 = p3 -ᵥ m - (p1 -ᵥ m) := (vsub_sub_vsub_cancel_right p3 p1 m).symm
  have h2 : p3 -ᵥ p2 = p3 -ᵥ m + (p1 -ᵥ m) := by
    rw [left_vsub_midpoint, ← midpoint_vsub_right, vsub_add_vsub_cancel]
  rw [dist_eq_norm_vsub V p3 p1, dist_eq_norm_vsub V p3 p2, h1, h2] at h
  exact (norm_add_eq_norm_sub_iff_angle_eq_pi_div_two (p3 -ᵥ m) (p1 -ᵥ m)).mp h.symm
#align euclidean_geometry.angle_left_midpoint_eq_pi_div_two_of_dist_eq EuclideanGeometry.angle_left_midpoint_eq_pi_div_two_of_dist_eq

/-- If M is the midpoint of the segment AB and C is the same distance from A as it is from B
then ∠CMB = π / 2. -/
theorem angle_right_midpoint_eq_pi_div_two_of_dist_eq {p1 p2 p3 : P} (h : dist p3 p1 = dist p3 p2) :
    ∠ p3 (midpoint ℝ p1 p2) p2 = π / 2 := by
  rw [midpoint_comm p1 p2, angle_left_midpoint_eq_pi_div_two_of_dist_eq h.symm]
#align euclidean_geometry.angle_right_midpoint_eq_pi_div_two_of_dist_eq EuclideanGeometry.angle_right_midpoint_eq_pi_div_two_of_dist_eq

/-- If the second of three points is strictly between the other two, the angle at that point
is π. -/
theorem Sbtw.angle₁₂₃_eq_pi {p₁ p₂ p₃ : P} (h : Sbtw ℝ p₁ p₂ p₃) : ∠ p₁ p₂ p₃ = π :=
  by
  rw [angle, angle_eq_pi_iff]
  rcases h with ⟨⟨r, ⟨hr0, hr1⟩, hp₂⟩, hp₂p₁, hp₂p₃⟩
  refine' ⟨vsub_ne_zero.2 hp₂p₁.symm, -(1 - r) / r, _⟩
  have hr0' : r ≠ 0 := by
    rintro rfl
    rw [← hp₂] at hp₂p₁
    simpa using hp₂p₁
  have hr1' : r ≠ 1 := by
    rintro rfl
    rw [← hp₂] at hp₂p₃
    simpa using hp₂p₃
  replace hr0 := hr0.lt_of_ne hr0'.symm
  replace hr1 := hr1.lt_of_ne hr1'
  refine' ⟨div_neg_of_neg_of_pos (Left.neg_neg_iff.2 (sub_pos.2 hr1)) hr0, _⟩
  rw [← hp₂, AffineMap.lineMap_apply, vsub_vadd_eq_vsub_sub, vsub_vadd_eq_vsub_sub, vsub_self,
    zero_sub, smul_neg, smul_smul, div_mul_cancel _ hr0', neg_smul, neg_neg, sub_eq_iff_eq_add, ←
    add_smul, sub_add_cancel, one_smul]
#align sbtw.angle₁₂₃_eq_pi Sbtw.angle₁₂₃_eq_pi

/-- If the second of three points is strictly between the other two, the angle at that point
(reversed) is π. -/
theorem Sbtw.angle₃₂₁_eq_pi {p₁ p₂ p₃ : P} (h : Sbtw ℝ p₁ p₂ p₃) : ∠ p₃ p₂ p₁ = π := by
  rw [← h.angle₁₂₃_eq_pi, angle_comm]
#align sbtw.angle₃₂₁_eq_pi Sbtw.angle₃₂₁_eq_pi

/-- The angle between three points is π if and only if the second point is strictly between the
other two. -/
theorem angle_eq_pi_iff_sbtw {p₁ p₂ p₃ : P} : ∠ p₁ p₂ p₃ = π ↔ Sbtw ℝ p₁ p₂ p₃ :=
  by
  refine' ⟨_, fun h => h.angle₁₂₃_eq_pi⟩
  rw [angle, angle_eq_pi_iff]
  rintro ⟨hp₁p₂, r, hr, hp₃p₂⟩
  refine'
    ⟨⟨1 / (1 - r),
        ⟨div_nonneg zero_le_one (sub_nonneg.2 (hr.le.trans zero_le_one)),
          (div_le_one (sub_pos.2 (hr.trans zero_lt_one))).2 ((le_sub_self_iff 1).2 hr.le)⟩,
        _⟩,
      (vsub_ne_zero.1 hp₁p₂).symm, _⟩
  · rw [← eq_vadd_iff_vsub_eq] at hp₃p₂
    rw [AffineMap.lineMap_apply, hp₃p₂, vadd_vsub_assoc, ← neg_vsub_eq_vsub_rev p₂ p₁, smul_neg, ←
      neg_smul, smul_add, smul_smul, ← add_smul, eq_comm, eq_vadd_iff_vsub_eq]
    convert(one_smul ℝ (p₂ -ᵥ p₁)).symm
    field_simp [(sub_pos.2 (hr.trans zero_lt_one)).Ne.symm]
    abel
  · rw [ne_comm, ← @vsub_ne_zero V, hp₃p₂, smul_ne_zero_iff]
    exact ⟨hr.ne, hp₁p₂⟩
#align euclidean_geometry.angle_eq_pi_iff_sbtw EuclideanGeometry.angle_eq_pi_iff_sbtw

/-- If the second of three points is weakly between the other two, and not equal to the first,
the angle at the first point is zero. -/
theorem Wbtw.angle₂₁₃_eq_zero_of_ne {p₁ p₂ p₃ : P} (h : Wbtw ℝ p₁ p₂ p₃) (hp₂p₁ : p₂ ≠ p₁) :
    ∠ p₂ p₁ p₃ = 0 := by
  rw [angle, angle_eq_zero_iff]
  rcases h with ⟨r, ⟨hr0, hr1⟩, rfl⟩
  have hr0' : r ≠ 0 := by
    rintro rfl
    simpa using hp₂p₁
  replace hr0 := hr0.lt_of_ne hr0'.symm
  refine' ⟨vsub_ne_zero.2 hp₂p₁, r⁻¹, inv_pos.2 hr0, _⟩
  rw [AffineMap.lineMap_apply, vadd_vsub_assoc, vsub_self, add_zero, smul_smul, inv_mul_cancel hr0',
    one_smul]
#align wbtw.angle₂₁₃_eq_zero_of_ne Wbtw.angle₂₁₃_eq_zero_of_ne

/-- If the second of three points is strictly between the other two, the angle at the first point
is zero. -/
theorem Sbtw.angle₂₁₃_eq_zero {p₁ p₂ p₃ : P} (h : Sbtw ℝ p₁ p₂ p₃) : ∠ p₂ p₁ p₃ = 0 :=
  h.Wbtw.angle₂₁₃_eq_zero_of_ne h.ne_left
#align sbtw.angle₂₁₃_eq_zero Sbtw.angle₂₁₃_eq_zero

/-- If the second of three points is weakly between the other two, and not equal to the first,
the angle at the first point (reversed) is zero. -/
theorem Wbtw.angle₃₁₂_eq_zero_of_ne {p₁ p₂ p₃ : P} (h : Wbtw ℝ p₁ p₂ p₃) (hp₂p₁ : p₂ ≠ p₁) :
    ∠ p₃ p₁ p₂ = 0 := by rw [← h.angle₂₁₃_eq_zero_of_ne hp₂p₁, angle_comm]
#align wbtw.angle₃₁₂_eq_zero_of_ne Wbtw.angle₃₁₂_eq_zero_of_ne

/-- If the second of three points is strictly between the other two, the angle at the first point
(reversed) is zero. -/
theorem Sbtw.angle₃₁₂_eq_zero {p₁ p₂ p₃ : P} (h : Sbtw ℝ p₁ p₂ p₃) : ∠ p₃ p₁ p₂ = 0 :=
  h.Wbtw.angle₃₁₂_eq_zero_of_ne h.ne_left
#align sbtw.angle₃₁₂_eq_zero Sbtw.angle₃₁₂_eq_zero

/-- If the second of three points is weakly between the other two, and not equal to the third,
the angle at the third point is zero. -/
theorem Wbtw.angle₂₃₁_eq_zero_of_ne {p₁ p₂ p₃ : P} (h : Wbtw ℝ p₁ p₂ p₃) (hp₂p₃ : p₂ ≠ p₃) :
    ∠ p₂ p₃ p₁ = 0 :=
  h.symm.angle₂₁₃_eq_zero_of_ne hp₂p₃
#align wbtw.angle₂₃₁_eq_zero_of_ne Wbtw.angle₂₃₁_eq_zero_of_ne

/-- If the second of three points is strictly between the other two, the angle at the third point
is zero. -/
theorem Sbtw.angle₂₃₁_eq_zero {p₁ p₂ p₃ : P} (h : Sbtw ℝ p₁ p₂ p₃) : ∠ p₂ p₃ p₁ = 0 :=
  h.Wbtw.angle₂₃₁_eq_zero_of_ne h.ne_right
#align sbtw.angle₂₃₁_eq_zero Sbtw.angle₂₃₁_eq_zero

/-- If the second of three points is weakly between the other two, and not equal to the third,
the angle at the third point (reversed) is zero. -/
theorem Wbtw.angle₁₃₂_eq_zero_of_ne {p₁ p₂ p₃ : P} (h : Wbtw ℝ p₁ p₂ p₃) (hp₂p₃ : p₂ ≠ p₃) :
    ∠ p₁ p₃ p₂ = 0 :=
  h.symm.angle₃₁₂_eq_zero_of_ne hp₂p₃
#align wbtw.angle₁₃₂_eq_zero_of_ne Wbtw.angle₁₃₂_eq_zero_of_ne

/-- If the second of three points is strictly between the other two, the angle at the third point
(reversed) is zero. -/
theorem Sbtw.angle₁₃₂_eq_zero {p₁ p₂ p₃ : P} (h : Sbtw ℝ p₁ p₂ p₃) : ∠ p₁ p₃ p₂ = 0 :=
  h.Wbtw.angle₁₃₂_eq_zero_of_ne h.ne_right
#align sbtw.angle₁₃₂_eq_zero Sbtw.angle₁₃₂_eq_zero

/-- The angle between three points is zero if and only if one of the first and third points is
weakly between the other two, and not equal to the second. -/
theorem angle_eq_zero_iff_ne_and_wbtw {p₁ p₂ p₃ : P} :
    ∠ p₁ p₂ p₃ = 0 ↔ p₁ ≠ p₂ ∧ Wbtw ℝ p₂ p₁ p₃ ∨ p₃ ≠ p₂ ∧ Wbtw ℝ p₂ p₃ p₁ :=
  by
  constructor
  · rw [angle, angle_eq_zero_iff]
    rintro ⟨hp₁p₂, r, hr0, hp₃p₂⟩
    rcases le_or_lt 1 r with (hr1 | hr1)
    · refine' Or.inl ⟨vsub_ne_zero.1 hp₁p₂, r⁻¹, ⟨(inv_pos.2 hr0).le, inv_le_one hr1⟩, _⟩
      rw [AffineMap.lineMap_apply, hp₃p₂, smul_smul, inv_mul_cancel hr0.ne.symm, one_smul,
        vsub_vadd]
    · refine' Or.inr ⟨_, r, ⟨hr0.le, hr1.le⟩, _⟩
      · rw [← @vsub_ne_zero V, hp₃p₂, smul_ne_zero_iff]
        exact ⟨hr0.ne.symm, hp₁p₂⟩
      · rw [AffineMap.lineMap_apply, ← hp₃p₂, vsub_vadd]
  · rintro (⟨hp₁p₂, h⟩ | ⟨hp₃p₂, h⟩)
    · exact h.angle₂₁₃_eq_zero_of_ne hp₁p₂
    · exact h.angle₃₁₂_eq_zero_of_ne hp₃p₂
#align euclidean_geometry.angle_eq_zero_iff_ne_and_wbtw EuclideanGeometry.angle_eq_zero_iff_ne_and_wbtw

/-- The angle between three points is zero if and only if one of the first and third points is
strictly between the other two, or those two points are equal but not equal to the second. -/
theorem angle_eq_zero_iff_eq_and_ne_or_sbtw {p₁ p₂ p₃ : P} :
    ∠ p₁ p₂ p₃ = 0 ↔ p₁ = p₃ ∧ p₁ ≠ p₂ ∨ Sbtw ℝ p₂ p₁ p₃ ∨ Sbtw ℝ p₂ p₃ p₁ :=
  by
  rw [angle_eq_zero_iff_ne_and_wbtw]
  by_cases hp₁p₂ : p₁ = p₂; · simp [hp₁p₂]
  by_cases hp₁p₃ : p₁ = p₃; · simp [hp₁p₃]
  by_cases hp₃p₂ : p₃ = p₂; · simp [hp₃p₂]
  simp [hp₁p₂, hp₁p₃, Ne.symm hp₁p₃, Sbtw, hp₃p₂]
#align euclidean_geometry.angle_eq_zero_iff_eq_and_ne_or_sbtw EuclideanGeometry.angle_eq_zero_iff_eq_and_ne_or_sbtw

/-- Three points are collinear if and only if the first or third point equals the second or the
angle between them is 0 or π. -/
theorem collinear_iff_eq_or_eq_or_angle_eq_zero_or_angle_eq_pi {p₁ p₂ p₃ : P} :
    Collinear ℝ ({p₁, p₂, p₃} : Set P) ↔ p₁ = p₂ ∨ p₃ = p₂ ∨ ∠ p₁ p₂ p₃ = 0 ∨ ∠ p₁ p₂ p₃ = π :=
  by
  refine' ⟨fun h => _, fun h => _⟩
  · replace h := h.wbtw_or_wbtw_or_wbtw
    by_cases h₁₂ : p₁ = p₂
    · exact Or.inl h₁₂
    by_cases h₃₂ : p₃ = p₂
    · exact Or.inr (Or.inl h₃₂)
    rw [or_iff_right h₁₂, or_iff_right h₃₂]
    rcases h with (h | h | h)
    · exact Or.inr (angle_eq_pi_iff_sbtw.2 ⟨h, Ne.symm h₁₂, Ne.symm h₃₂⟩)
    · exact Or.inl (h.angle₃₁₂_eq_zero_of_ne h₃₂)
    · exact Or.inl (h.angle₂₃₁_eq_zero_of_ne h₁₂)
  · rcases h with (rfl | rfl | h | h)
    · simpa using collinear_pair ℝ p₁ p₃
    · simpa using collinear_pair ℝ p₁ p₃
    · rw [angle_eq_zero_iff_ne_and_wbtw] at h
      rcases h with (⟨-, h⟩ | ⟨-, h⟩)
      · rw [Set.insert_comm]
        exact h.collinear
      · rw [Set.insert_comm, Set.pair_comm]
        exact h.collinear
    · rw [angle_eq_pi_iff_sbtw] at h
      exact h.wbtw.collinear
#align euclidean_geometry.collinear_iff_eq_or_eq_or_angle_eq_zero_or_angle_eq_pi EuclideanGeometry.collinear_iff_eq_or_eq_or_angle_eq_zero_or_angle_eq_pi

/-- If the angle between three points is 0, they are collinear. -/
theorem collinear_of_angle_eq_zero {p₁ p₂ p₃ : P} (h : ∠ p₁ p₂ p₃ = 0) :
    Collinear ℝ ({p₁, p₂, p₃} : Set P) :=
  collinear_iff_eq_or_eq_or_angle_eq_zero_or_angle_eq_pi.2 <| Or.inr <| Or.inr <| Or.inl h
#align euclidean_geometry.collinear_of_angle_eq_zero EuclideanGeometry.collinear_of_angle_eq_zero

/-- If the angle between three points is π, they are collinear. -/
theorem collinear_of_angle_eq_pi {p₁ p₂ p₃ : P} (h : ∠ p₁ p₂ p₃ = π) :
    Collinear ℝ ({p₁, p₂, p₃} : Set P) :=
  collinear_iff_eq_or_eq_or_angle_eq_zero_or_angle_eq_pi.2 <| Or.inr <| Or.inr <| Or.inr h
#align euclidean_geometry.collinear_of_angle_eq_pi EuclideanGeometry.collinear_of_angle_eq_pi

/-- If three points are not collinear, the angle between them is nonzero. -/
theorem angle_ne_zero_of_not_collinear {p₁ p₂ p₃ : P} (h : ¬Collinear ℝ ({p₁, p₂, p₃} : Set P)) :
    ∠ p₁ p₂ p₃ ≠ 0 :=
  mt collinear_of_angle_eq_zero h
#align euclidean_geometry.angle_ne_zero_of_not_collinear EuclideanGeometry.angle_ne_zero_of_not_collinear

/-- If three points are not collinear, the angle between them is not π. -/
theorem angle_ne_pi_of_not_collinear {p₁ p₂ p₃ : P} (h : ¬Collinear ℝ ({p₁, p₂, p₃} : Set P)) :
    ∠ p₁ p₂ p₃ ≠ π :=
  mt collinear_of_angle_eq_pi h
#align euclidean_geometry.angle_ne_pi_of_not_collinear EuclideanGeometry.angle_ne_pi_of_not_collinear

/-- If three points are not collinear, the angle between them is positive. -/
theorem angle_pos_of_not_collinear {p₁ p₂ p₃ : P} (h : ¬Collinear ℝ ({p₁, p₂, p₃} : Set P)) :
    0 < ∠ p₁ p₂ p₃ :=
  (angle_nonneg _ _ _).lt_of_ne (angle_ne_zero_of_not_collinear h).symm
#align euclidean_geometry.angle_pos_of_not_collinear EuclideanGeometry.angle_pos_of_not_collinear

/-- If three points are not collinear, the angle between them is less than π. -/
theorem angle_lt_pi_of_not_collinear {p₁ p₂ p₃ : P} (h : ¬Collinear ℝ ({p₁, p₂, p₃} : Set P)) :
    ∠ p₁ p₂ p₃ < π :=
  (angle_le_pi _ _ _).lt_of_ne <| angle_ne_pi_of_not_collinear h
#align euclidean_geometry.angle_lt_pi_of_not_collinear EuclideanGeometry.angle_lt_pi_of_not_collinear

/-- The cosine of the angle between three points is 1 if and only if the angle is 0. -/
theorem cos_eq_one_iff_angle_eq_zero {p₁ p₂ p₃ : P} : Real.cos (∠ p₁ p₂ p₃) = 1 ↔ ∠ p₁ p₂ p₃ = 0 :=
  cos_eq_one_iff_angle_eq_zero
#align euclidean_geometry.cos_eq_one_iff_angle_eq_zero EuclideanGeometry.cos_eq_one_iff_angle_eq_zero

/-- The cosine of the angle between three points is 0 if and only if the angle is π / 2. -/
theorem cos_eq_zero_iff_angle_eq_pi_div_two {p₁ p₂ p₃ : P} :
    Real.cos (∠ p₁ p₂ p₃) = 0 ↔ ∠ p₁ p₂ p₃ = π / 2 :=
  cos_eq_zero_iff_angle_eq_pi_div_two
#align euclidean_geometry.cos_eq_zero_iff_angle_eq_pi_div_two EuclideanGeometry.cos_eq_zero_iff_angle_eq_pi_div_two

/-- The cosine of the angle between three points is -1 if and only if the angle is π. -/
theorem cos_eq_neg_one_iff_angle_eq_pi {p₁ p₂ p₃ : P} :
    Real.cos (∠ p₁ p₂ p₃) = -1 ↔ ∠ p₁ p₂ p₃ = π :=
  cos_eq_neg_one_iff_angle_eq_pi
#align euclidean_geometry.cos_eq_neg_one_iff_angle_eq_pi EuclideanGeometry.cos_eq_neg_one_iff_angle_eq_pi

/-- The sine of the angle between three points is 0 if and only if the angle is 0 or π. -/
theorem sin_eq_zero_iff_angle_eq_zero_or_angle_eq_pi {p₁ p₂ p₃ : P} :
    Real.sin (∠ p₁ p₂ p₃) = 0 ↔ ∠ p₁ p₂ p₃ = 0 ∨ ∠ p₁ p₂ p₃ = π :=
  sin_eq_zero_iff_angle_eq_zero_or_angle_eq_pi
#align euclidean_geometry.sin_eq_zero_iff_angle_eq_zero_or_angle_eq_pi EuclideanGeometry.sin_eq_zero_iff_angle_eq_zero_or_angle_eq_pi

/-- The sine of the angle between three points is 1 if and only if the angle is π / 2. -/
theorem sin_eq_one_iff_angle_eq_pi_div_two {p₁ p₂ p₃ : P} :
    Real.sin (∠ p₁ p₂ p₃) = 1 ↔ ∠ p₁ p₂ p₃ = π / 2 :=
  sin_eq_one_iff_angle_eq_pi_div_two
#align euclidean_geometry.sin_eq_one_iff_angle_eq_pi_div_two EuclideanGeometry.sin_eq_one_iff_angle_eq_pi_div_two

/-- Three points are collinear if and only if the first or third point equals the second or
the sine of the angle between three points is zero. -/
theorem collinear_iff_eq_or_eq_or_sin_eq_zero {p₁ p₂ p₃ : P} :
    Collinear ℝ ({p₁, p₂, p₃} : Set P) ↔ p₁ = p₂ ∨ p₃ = p₂ ∨ Real.sin (∠ p₁ p₂ p₃) = 0 := by
  rw [sin_eq_zero_iff_angle_eq_zero_or_angle_eq_pi,
    collinear_iff_eq_or_eq_or_angle_eq_zero_or_angle_eq_pi]
#align euclidean_geometry.collinear_iff_eq_or_eq_or_sin_eq_zero EuclideanGeometry.collinear_iff_eq_or_eq_or_sin_eq_zero

/-- If three points are not collinear, the sine of the angle between them is positive. -/
theorem sin_pos_of_not_collinear {p₁ p₂ p₃ : P} (h : ¬Collinear ℝ ({p₁, p₂, p₃} : Set P)) :
    0 < Real.sin (∠ p₁ p₂ p₃) :=
  Real.sin_pos_of_pos_of_lt_pi (angle_pos_of_not_collinear h) (angle_lt_pi_of_not_collinear h)
#align euclidean_geometry.sin_pos_of_not_collinear EuclideanGeometry.sin_pos_of_not_collinear

/-- If three points are not collinear, the sine of the angle between them is nonzero. -/
theorem sin_ne_zero_of_not_collinear {p₁ p₂ p₃ : P} (h : ¬Collinear ℝ ({p₁, p₂, p₃} : Set P)) :
    Real.sin (∠ p₁ p₂ p₃) ≠ 0 :=
  ne_of_gt (sin_pos_of_not_collinear h)
#align euclidean_geometry.sin_ne_zero_of_not_collinear EuclideanGeometry.sin_ne_zero_of_not_collinear

/-- If the sine of the angle between three points is 0, they are collinear. -/
theorem collinear_of_sin_eq_zero {p₁ p₂ p₃ : P} (h : Real.sin (∠ p₁ p₂ p₃) = 0) :
    Collinear ℝ ({p₁, p₂, p₃} : Set P) :=
  imp_of_not_imp_not _ _ sin_ne_zero_of_not_collinear h
#align euclidean_geometry.collinear_of_sin_eq_zero EuclideanGeometry.collinear_of_sin_eq_zero

end EuclideanGeometry


/-!
# Dependent de Bruijn Indices

In this example, we represent program syntax terms in a type family parameterized by a list of types,
representing the typing context, or information on which free variables are in scope and what
their types are.

Remark: this example is based on an example in the book [Certified Programming with Dependent Types](http://adam.chlipala.net/cpdt/) by Adam Chlipala.
-/

/-!
Programmers who move to statically typed functional languages from scripting languages
often complain about the requirement that every element of a list have the same type. With
fancy type systems, we can partially lift this requirement. We can index a list type with a
“type-level” list that explains what type each element of the list should have. This has been
done in a variety of ways in Haskell using type classes, and we can do it much more cleanly
and directly in Lean.

We parameterize our heterogeneous lists by at type `α` and an `α`-indexed type `β`.
-/

inductive HList {α : Type v} (β : α → Type u) : List α → Type (max u v)
  | nil  : HList β []
  | cons : β i → HList β is → HList β (i::is)

/-!
We overload the `List.cons` notation `::` so we can also use it to create
heterogeneous lists.
-/
infix:67 " :: " => HList.cons

/-! We similarly overload the `List` notation `[]` for the empty heterogeneous list. -/
notation "[" "]" => HList.nil

/-!
Variables are represented in a way isomorphic to the natural numbers, where
number 0 represents the first element in the context, number 1 the second element, and so
on. Actually, instead of numbers, we use the `Member` inductive family.

The value of type `Member a as` can be viewed as a certificate that `a` is
an element of the list `as`. The constructor `Member.head` says that `a`
is in the list if the list begins with it. The constructor `Member.tail`
says that if `a` is in the list `bs`, it is also in the list `b::bs`.
-/
inductive Member : α → List α → Type
  | head : Member a (a::as)
  | tail : Member a bs → Member a (b::bs)

/-!
Given a heterogeneous list `HList β is` and value of type `Member i is`, `HList.get`
retrieves an element of type `β i` from the list.
The pattern `.head` and `.tail h` are sugar for `Member.head` and `Member.tail h` respectively.
Lean can infer the namespace using the expected type.
-/
def HList.get : HList β is → Member i is → β i
  | a::as, .head => a
  | a::as, .tail h => as.get h

/-!
Here is the definition of the simple type system for our programming language, a simply typed
lambda calculus with natural numbers as the base type.
-/
inductive Ty where
  | nat
  | fn : Ty → Ty → Ty

/-!
We can write a function to translate `Ty` values to a Lean type
— remember that types are first class, so can be calculated just like any other value.
We mark `Ty.denote` as `[reducible]` to make sure the typeclass resolution procedure can
unfold/reduce it. For example, suppose Lean is trying to synthesize a value for the instance
`Add (Ty.denote Ty.nat)`. Since `Ty.denote` is marked as `[reducible]`,
the typeclass resolution procedure can reduce `Ty.denote Ty.nat` to `Nat`, and use
the builtin instance for `Add Nat` as the solution.

Recall that the term `a.denote` is sugar for `denote a` where `denote` is the function being defined.
We call it the "dot notation".
-/
@[reducible] def Ty.denote : Ty → Type
  | nat    => Nat
  | fn a b => a.denote → b.denote

/-!
Here is the definition of the `Term` type, including variables, constants, addition,
function application and abstraction, and let binding of local variables.
Since `let` is a keyword in Lean, we use the "escaped identifier" `«let»`.
You can input the unicode (French double quotes) using `\f<<` (for `«`) and `\f>>` (for `»`).
The term `Term ctx .nat` is sugar for `Term ctx Ty.nat`, Lean infers the namespace using the expected type.
-/
inductive Term : List Ty → Ty → Type
  | var   : Member ty ctx → Term ctx ty
  | const : Nat → Term ctx .nat
  | plus  : Term ctx .nat → Term ctx .nat → Term ctx .nat
  | app   : Term ctx (.fn dom ran) → Term ctx dom → Term ctx ran
  | lam   : Term (dom :: ctx) ran → Term ctx (.fn dom ran)
  | «let» : Term ctx ty₁ → Term (ty₁ :: ctx) ty₂ → Term ctx ty₂

/-!
Here are two example terms encoding, the first addition packaged as a two-argument
curried function, and the second of a sample application of addition to constants.

The command `open Ty Term Member` opens the namespaces `Ty`, `Term`, and `Member`. Thus,
you can write `lam` instead of `Term.lam`.
-/
open Ty Term Member
def add : Term [] (fn nat (fn nat nat)) :=
  lam (lam (plus (var (tail head)) (var head)))

def three_the_hard_way : Term [] nat :=
  app (app add (const 1)) (const 2)

/-!
Since dependent typing ensures that any term is well-formed in its context and has a particular type,
it is easy to translate syntactic terms into Lean values.

The attribute `[simp]` instructs Lean to always try to unfold `Term.denote` applications when one applies
the `simp` tactic. We also say this is a hint for the Lean term simplifier.
-/
@[simp] def Term.denote : Term ctx ty → HList Ty.denote ctx → ty.denote
  | var h,     env => env.get h
  | const n,   _   => n
  | plus a b,  env => a.denote env + b.denote env
  | app f a,   env => f.denote env (a.denote env)
  | lam b,     env => fun x => b.denote (x :: env)
  | «let» a b, env => b.denote (a.denote env :: env)

/-!
You can show that the denotation of `three_the_hard_way` is indeed `3` using reflexivity.
-/
example : three_the_hard_way.denote [] = 3 :=
  rfl

/-!
We now define the constant folding optimization that traverses a term if replaces subterms such as
`plus (const m) (const n)` with `const (n+m)`.
-/
@[simp] def Term.constFold : Term ctx ty → Term ctx ty
  | const n   => const n
  | var h     => var h
  | app f a   => app f.constFold a.constFold
  | lam b     => lam b.constFold
  | «let» a b => «let» a.constFold b.constFold
  | plus a b  =>
    match a.constFold, b.constFold with
    | const n, const m => const (n+m)
    | a',      b'      => plus a' b'

/-!
The correctness of the `Term.constFold` is proved using induction, case-analysis, and the term simplifier.
We prove all cases but the one for `plus` using `simp [*]`. This tactic instructs the term simplifier to
use hypotheses such as `a = b` as rewriting/simplications rules.
We use the `split` to break the nested `match` expression in the `plus` case into two cases.
The local variables `iha` and `ihb` are the induction hypotheses for `a` and `b`.
The modifier `←` in a term simplifier argument instructs the term simplier to use the equation as a rewriting rule in
the "reverse direction". That is, given `h : a = b`, `← h` instructs the term simplifier to rewrite `b` subterms to `a`.
-/
theorem Term.constFold_sound (e : Term ctx ty) : e.constFold.denote env = e.denote env := by
  induction e with simp [*]
  | plus a b iha ihb =>
    split
    next he₁ he₂ => simp [← iha, ← ihb, he₁, he₂]
    next => simp [iha, ihb]


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

! This file was ported from Lean 3 source module group_theory.commensurable
! leanprover-community/mathlib commit e97cf15cd1aec9bd5c193b2ffac5a6dc9118912b
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.GroupTheory.Index
import Mathbin.GroupTheory.Subgroup.Pointwise
import Mathbin.GroupTheory.GroupAction.ConjAct

/-!
# Commensurability for subgroups

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

This file defines commensurability for subgroups of a group `G`. It then goes on to prove that
commensurability defines an equivalence relation and finally defines the commensurator of a subgroup
of `G`.

## Main definitions

* `commensurable`: defines commensurability for two subgroups `H`, `K` of  `G`
* `commensurator`: defines the commensurator of a subgroup `H` of `G`.

## Implementation details

We define the commensurator of a subgroup `H` of `G` by first defining it as a subgroup of
`(conj_act G)`, which we call commensurator' and then taking the pre-image under
the map `G → (conj_act G)` to obtain our commensurator as a subgroup of `G`.
-/

variable {G : Type _} [Group G]

#print Commensurable /-
/-- Two subgroups `H K` of `G` are commensurable if `H ⊓ K` has finite index in both `H` and `K` -/
def Commensurable (H K : Subgroup G) : Prop :=
  H.relindex K ≠ 0 ∧ K.relindex H ≠ 0
#align commensurable Commensurable
-/

namespace Commensurable

open Pointwise

#print Commensurable.refl /-
@[refl]
protected theorem refl (H : Subgroup G) : Commensurable H H := by simp [Commensurable]
#align commensurable.refl Commensurable.refl
-/

#print Commensurable.comm /-
theorem comm {H K : Subgroup G} : Commensurable H K ↔ Commensurable K H :=
  and_comm
#align commensurable.comm Commensurable.comm
-/

#print Commensurable.symm /-
@[symm]
theorem symm {H K : Subgroup G} : Commensurable H K → Commensurable K H :=
  And.symm
#align commensurable.symm Commensurable.symm
-/

#print Commensurable.trans /-
@[trans]
theorem trans {H K L : Subgroup G} (hhk : Commensurable H K) (hkl : Commensurable K L) :
    Commensurable H L :=
  ⟨Subgroup.relindex_ne_zero_trans hhk.1 hkl.1, Subgroup.relindex_ne_zero_trans hkl.2 hhk.2⟩
#align commensurable.trans Commensurable.trans
-/

#print Commensurable.equivalence /-
theorem equivalence : Equivalence (@Commensurable G _) :=
  ⟨Commensurable.refl, fun _ _ => Commensurable.symm, fun _ _ _ => Commensurable.trans⟩
#align commensurable.equivalence Commensurable.equivalence
-/

/- warning: commensurable.quot_conj_equiv -> Commensurable.quotConjEquiv is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] (H : Subgroup.{u1} G _inst_1) (K : Subgroup.{u1} G _inst_1) (g : ConjAct.{u1} G), Equiv.{succ u1, succ u1} (HasQuotient.Quotient.{u1, u1} (coeSort.{succ u1, succ (succ u1)} (Subgroup.{u1} G _inst_1) Type.{u1} (SetLike.hasCoeToSort.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.setLike.{u1} G _inst_1)) K) (Subgroup.{u1} (coeSort.{succ u1, succ (succ u1)} (Subgroup.{u1} G _inst_1) Type.{u1} (SetLike.hasCoeToSort.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.setLike.{u1} G _inst_1)) K) (Subgroup.toGroup.{u1} G _inst_1 K)) (QuotientGroup.Subgroup.hasQuotient.{u1} (coeSort.{succ u1, succ (succ u1)} (Subgroup.{u1} G _inst_1) Type.{u1} (SetLike.hasCoeToSort.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.setLike.{u1} G _inst_1)) K) (Subgroup.toGroup.{u1} G _inst_1 K)) (Subgroup.subgroupOf.{u1} G _inst_1 H K)) (HasQuotient.Quotient.{u1, u1} (coeSort.{succ u1, succ (succ u1)} (Set.{u1} G) Type.{u1} (Set.hasCoeToSort.{u1} G) (Subgroup.carrier.{u1} G _inst_1 (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) g K))) (Subgroup.{u1} (coeSort.{succ u1, succ (succ u1)} (Subgroup.{u1} G _inst_1) Type.{u1} (SetLike.hasCoeToSort.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.setLike.{u1} G _inst_1)) (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) g K)) (Subgroup.toGroup.{u1} G _inst_1 (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) g K))) (QuotientGroup.Subgroup.hasQuotient.{u1} (coeSort.{succ u1, succ (succ u1)} (Subgroup.{u1} G _inst_1) Type.{u1} (SetLike.hasCoeToSort.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.setLike.{u1} G _inst_1)) (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) g K)) (Subgroup.toGroup.{u1} G _inst_1 (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) g K))) (Subgroup.subgroupOf.{u1} G _inst_1 (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) g H) (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) g K)))
but is expected to have type
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] (H : Subgroup.{u1} G _inst_1) (K : Subgroup.{u1} G _inst_1) (g : ConjAct.{u1} G), Equiv.{succ u1, succ u1} (HasQuotient.Quotient.{u1, u1} (Subtype.{succ u1} G (fun (x : G) => Membership.mem.{u1, u1} G (Subgroup.{u1} G _inst_1) (SetLike.instMembership.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.instSetLikeSubgroup.{u1} G _inst_1)) x K)) (Subgroup.{u1} (Subtype.{succ u1} G (fun (x : G) => Membership.mem.{u1, u1} G (Subgroup.{u1} G _inst_1) (SetLike.instMembership.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.instSetLikeSubgroup.{u1} G _inst_1)) x K)) (Subgroup.toGroup.{u1} G _inst_1 K)) (QuotientGroup.instHasQuotientSubgroup.{u1} (Subtype.{succ u1} G (fun (x : G) => Membership.mem.{u1, u1} G (Subgroup.{u1} G _inst_1) (SetLike.instMembership.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.instSetLikeSubgroup.{u1} G _inst_1)) x K)) (Subgroup.toGroup.{u1} G _inst_1 K)) (Subgroup.subgroupOf.{u1} G _inst_1 H K)) (HasQuotient.Quotient.{u1, u1} (Subtype.{succ u1} G (fun (x : G) => Membership.mem.{u1, u1} G (Submonoid.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (SetLike.instMembership.{u1, u1} (Submonoid.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) G (Submonoid.instSetLikeSubmonoid.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))))) x (Subgroup.toSubmonoid.{u1} G _inst_1 (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) g K)))) (Subgroup.{u1} (Subtype.{succ u1} G (fun (x : G) => Membership.mem.{u1, u1} G (Subgroup.{u1} G _inst_1) (SetLike.instMembership.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.instSetLikeSubgroup.{u1} G _inst_1)) x (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) g K))) (Subgroup.toGroup.{u1} G _inst_1 (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) g K))) (QuotientGroup.instHasQuotientSubgroup.{u1} (Subtype.{succ u1} G (fun (x : G) => Membership.mem.{u1, u1} G (Subgroup.{u1} G _inst_1) (SetLike.instMembership.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.instSetLikeSubgroup.{u1} G _inst_1)) x (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) g K))) (Subgroup.toGroup.{u1} G _inst_1 (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) g K))) (Subgroup.subgroupOf.{u1} G _inst_1 (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) g H) (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) g K)))
Case conversion may be inaccurate. Consider using '#align commensurable.quot_conj_equiv Commensurable.quotConjEquivₓ'. -/
/-- Equivalence of `K/H ⊓ K` with `gKg⁻¹/gHg⁻¹ ⊓ gKg⁻¹`-/
def quotConjEquiv (H K : Subgroup G) (g : ConjAct G) :
    K ⧸ H.subgroupOf K ≃ (g • K).1 ⧸ (g • H).subgroupOf (g • K) :=
  Quotient.congr (K.equivSMul g).toEquiv fun a b =>
    by
    dsimp
    rw [← Quotient.eq'', ← Quotient.eq'', QuotientGroup.eq', QuotientGroup.eq',
      Subgroup.mem_subgroupOf, Subgroup.mem_subgroupOf, ← MulEquiv.map_inv, ← MulEquiv.map_mul,
      Subgroup.equivSMul_apply_coe]
    exact subgroup.smul_mem_pointwise_smul_iff.symm
#align commensurable.quot_conj_equiv Commensurable.quotConjEquiv

/- warning: commensurable.commensurable_conj -> Commensurable.commensurable_conj is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] {H : Subgroup.{u1} G _inst_1} {K : Subgroup.{u1} G _inst_1} (g : ConjAct.{u1} G), Iff (Commensurable.{u1} G _inst_1 H K) (Commensurable.{u1} G _inst_1 (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) g H) (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) g K))
but is expected to have type
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] {H : Subgroup.{u1} G _inst_1} {K : Subgroup.{u1} G _inst_1} (g : ConjAct.{u1} G), Iff (Commensurable.{u1} G _inst_1 H K) (Commensurable.{u1} G _inst_1 (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) g H) (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) g K))
Case conversion may be inaccurate. Consider using '#align commensurable.commensurable_conj Commensurable.commensurable_conjₓ'. -/
theorem commensurable_conj {H K : Subgroup G} (g : ConjAct G) :
    Commensurable H K ↔ Commensurable (g • H) (g • K) :=
  and_congr (not_iff_not.mpr (Eq.congr_left (Cardinal.toNat_congr (quotConjEquiv H K g))))
    (not_iff_not.mpr (Eq.congr_left (Cardinal.toNat_congr (quotConjEquiv K H g))))
#align commensurable.commensurable_conj Commensurable.commensurable_conj

/- warning: commensurable.commensurable_inv -> Commensurable.commensurable_inv is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] (H : Subgroup.{u1} G _inst_1) (g : ConjAct.{u1} G), Iff (Commensurable.{u1} G _inst_1 (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) g H) H) (Commensurable.{u1} G _inst_1 H (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) (Inv.inv.{u1} (ConjAct.{u1} G) (DivInvMonoid.toHasInv.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) g) H))
but is expected to have type
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] (H : Subgroup.{u1} G _inst_1) (g : ConjAct.{u1} G), Iff (Commensurable.{u1} G _inst_1 (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) g H) H) (Commensurable.{u1} G _inst_1 H (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) (Inv.inv.{u1} (ConjAct.{u1} G) (InvOneClass.toInv.{u1} (ConjAct.{u1} G) (DivInvOneMonoid.toInvOneClass.{u1} (ConjAct.{u1} G) (DivisionMonoid.toDivInvOneMonoid.{u1} (ConjAct.{u1} G) (Group.toDivisionMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instGroupConjAct.{u1} G _inst_1))))) g) H))
Case conversion may be inaccurate. Consider using '#align commensurable.commensurable_inv Commensurable.commensurable_invₓ'. -/
theorem commensurable_inv (H : Subgroup G) (g : ConjAct G) :
    Commensurable (g • H) H ↔ Commensurable H (g⁻¹ • H) := by rw [commensurable_conj, inv_smul_smul]
#align commensurable.commensurable_inv Commensurable.commensurable_inv

#print Commensurable.commensurator' /-
/-- For `H` a subgroup of `G`, this is the subgroup of all elements `g : conj_aut G`
such that `commensurable (g • H) H` -/
def commensurator' (H : Subgroup G) : Subgroup (ConjAct G)
    where
  carrier := { g : ConjAct G | Commensurable (g • H) H }
  one_mem' := by rw [Set.mem_setOf_eq, one_smul]
  mul_mem' a b ha hb := by
    rw [Set.mem_setOf_eq, mul_smul]
    exact trans ((commensurable_conj a).mp hb) ha
  inv_mem' a ha := by rwa [Set.mem_setOf_eq, comm, ← commensurable_inv]
#align commensurable.commensurator' Commensurable.commensurator'
-/

#print Commensurable.commensurator /-
/-- For `H` a subgroup of `G`, this is the subgroup of all elements `g : G`
such that `commensurable (g H g⁻¹) H` -/
def commensurator (H : Subgroup G) : Subgroup G :=
  (commensurator' H).comap ConjAct.toConjAct.toMonoidHom
#align commensurable.commensurator Commensurable.commensurator
-/

/- warning: commensurable.commensurator'_mem_iff -> Commensurable.commensurator'_mem_iff is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] (H : Subgroup.{u1} G _inst_1) (g : ConjAct.{u1} G), Iff (Membership.Mem.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} (ConjAct.{u1} G) (ConjAct.group.{u1} G _inst_1)) (SetLike.hasMem.{u1, u1} (Subgroup.{u1} (ConjAct.{u1} G) (ConjAct.group.{u1} G _inst_1)) (ConjAct.{u1} G) (Subgroup.setLike.{u1} (ConjAct.{u1} G) (ConjAct.group.{u1} G _inst_1))) g (Commensurable.commensurator'.{u1} G _inst_1 H)) (Commensurable.{u1} G _inst_1 (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) g H) H)
but is expected to have type
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] (H : Subgroup.{u1} G _inst_1) (g : ConjAct.{u1} G), Iff (Membership.mem.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} (ConjAct.{u1} G) (ConjAct.instGroupConjAct.{u1} G _inst_1)) (SetLike.instMembership.{u1, u1} (Subgroup.{u1} (ConjAct.{u1} G) (ConjAct.instGroupConjAct.{u1} G _inst_1)) (ConjAct.{u1} G) (Subgroup.instSetLikeSubgroup.{u1} (ConjAct.{u1} G) (ConjAct.instGroupConjAct.{u1} G _inst_1))) g (Commensurable.commensurator'.{u1} G _inst_1 H)) (Commensurable.{u1} G _inst_1 (HSMul.hSMul.{u1, u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) g H) H)
Case conversion may be inaccurate. Consider using '#align commensurable.commensurator'_mem_iff Commensurable.commensurator'_mem_iffₓ'. -/
@[simp]
theorem commensurator'_mem_iff (H : Subgroup G) (g : ConjAct G) :
    g ∈ commensurator' H ↔ Commensurable (g • H) H :=
  Iff.rfl
#align commensurable.commensurator'_mem_iff Commensurable.commensurator'_mem_iff

/- warning: commensurable.commensurator_mem_iff -> Commensurable.commensurator_mem_iff is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] (H : Subgroup.{u1} G _inst_1) (g : G), Iff (Membership.Mem.{u1, u1} G (Subgroup.{u1} G _inst_1) (SetLike.hasMem.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.setLike.{u1} G _inst_1)) g (Commensurable.commensurator.{u1} G _inst_1 H)) (Commensurable.{u1} G _inst_1 (SMul.smul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (MulAction.toHasSmul.{u1, u1} (ConjAct.{u1} G) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} (ConjAct.{u1} G) G _inst_1 (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.mulDistribMulAction.{u1} G _inst_1))) (coeFn.{succ u1, succ u1} (MulEquiv.{u1, u1} G (ConjAct.{u1} G) (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toHasMul.{u1} (ConjAct.{u1} G) (Monoid.toMulOneClass.{u1} (ConjAct.{u1} G) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) (fun (_x : MulEquiv.{u1, u1} G (ConjAct.{u1} G) (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toHasMul.{u1} (ConjAct.{u1} G) (Monoid.toMulOneClass.{u1} (ConjAct.{u1} G) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) => G -> (ConjAct.{u1} G)) (MulEquiv.hasCoeToFun.{u1, u1} G (ConjAct.{u1} G) (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toHasMul.{u1} (ConjAct.{u1} G) (Monoid.toMulOneClass.{u1} (ConjAct.{u1} G) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.divInvMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) (ConjAct.toConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)) g) H) H)
but is expected to have type
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] (H : Subgroup.{u1} G _inst_1) (g : G), Iff (Membership.mem.{u1, u1} G (Subgroup.{u1} G _inst_1) (SetLike.instMembership.{u1, u1} (Subgroup.{u1} G _inst_1) G (Subgroup.instSetLikeSubgroup.{u1} G _inst_1)) g (Commensurable.commensurator.{u1} G _inst_1 H)) (Commensurable.{u1} G _inst_1 (HSMul.hSMul.{u1, u1, u1} ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : G) => ConjAct.{u1} G) g) (Subgroup.{u1} G _inst_1) (Subgroup.{u1} G _inst_1) (instHSMul.{u1, u1} ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : G) => ConjAct.{u1} G) g) (Subgroup.{u1} G _inst_1) (MulAction.toSMul.{u1, u1} ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : G) => ConjAct.{u1} G) g) (Subgroup.{u1} G _inst_1) (DivInvMonoid.toMonoid.{u1} ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : G) => ConjAct.{u1} G) g) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (Subgroup.pointwiseMulAction.{u1, u1} ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : G) => ConjAct.{u1} G) g) G _inst_1 (DivInvMonoid.toMonoid.{u1} ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : G) => ConjAct.{u1} G) g) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))) (ConjAct.instMulDistribMulActionConjActToMonoidInstDivInvMonoidConjActToDivInvMonoid.{u1} G _inst_1)))) (FunLike.coe.{succ u1, succ u1, succ u1} (MulEquiv.{u1, u1} G (ConjAct.{u1} G) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toMul.{u1} (ConjAct.{u1} G) (Monoid.toMulOneClass.{u1} (ConjAct.{u1} G) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) G (fun (_x : G) => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : G) => ConjAct.{u1} G) _x) (EmbeddingLike.toFunLike.{succ u1, succ u1, succ u1} (MulEquiv.{u1, u1} G (ConjAct.{u1} G) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toMul.{u1} (ConjAct.{u1} G) (Monoid.toMulOneClass.{u1} (ConjAct.{u1} G) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) G (ConjAct.{u1} G) (EquivLike.toEmbeddingLike.{succ u1, succ u1, succ u1} (MulEquiv.{u1, u1} G (ConjAct.{u1} G) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toMul.{u1} (ConjAct.{u1} G) (Monoid.toMulOneClass.{u1} (ConjAct.{u1} G) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) G (ConjAct.{u1} G) (MulEquivClass.toEquivLike.{u1, u1, u1} (MulEquiv.{u1, u1} G (ConjAct.{u1} G) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toMul.{u1} (ConjAct.{u1} G) (Monoid.toMulOneClass.{u1} (ConjAct.{u1} G) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) G (ConjAct.{u1} G) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toMul.{u1} (ConjAct.{u1} G) (Monoid.toMulOneClass.{u1} (ConjAct.{u1} G) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))))) (MulEquiv.instMulEquivClassMulEquiv.{u1, u1} G (ConjAct.{u1} G) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toMul.{u1} (ConjAct.{u1} G) (Monoid.toMulOneClass.{u1} (ConjAct.{u1} G) (DivInvMonoid.toMonoid.{u1} (ConjAct.{u1} G) (ConjAct.instDivInvMonoidConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))))))))) (ConjAct.toConjAct.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)) g) H) H)
Case conversion may be inaccurate. Consider using '#align commensurable.commensurator_mem_iff Commensurable.commensurator_mem_iffₓ'. -/
@[simp]
theorem commensurator_mem_iff (H : Subgroup G) (g : G) :
    g ∈ commensurator H ↔ Commensurable (ConjAct.toConjAct g • H) H :=
  Iff.rfl
#align commensurable.commensurator_mem_iff Commensurable.commensurator_mem_iff

#print Commensurable.eq /-
theorem eq {H K : Subgroup G} (hk : Commensurable H K) : commensurator H = commensurator K :=
  Subgroup.ext fun x =>
    let hx := (commensurable_conj x).1 hk
    ⟨fun h => hx.symm.trans (h.trans hk), fun h => hx.trans (h.trans hk.symm)⟩
#align commensurable.eq Commensurable.eq
-/

end Commensurable


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

! This file was ported from Lean 3 source module measure_theory.function.ess_sup
! leanprover-community/mathlib commit 394f6e63f63ebc49b2b723e62f89f1604aa4b87d
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.MeasureTheory.Constructions.BorelSpace
import Mathbin.Order.Filter.Ennreal

/-!
# Essential supremum and infimum
We define the essential supremum and infimum of a function `f : α → β` with respect to a measure
`μ` on `α`. The essential supremum is the infimum of the constants `c : β` such that `f x ≤ c`
almost everywhere.

TODO: The essential supremum of functions `α → ℝ≥0∞` is used in particular to define the norm in
the `L∞` space (see measure_theory/lp_space.lean).

There is a different quantity which is sometimes also called essential supremum: the least
upper-bound among measurable functions of a family of measurable functions (in an almost-everywhere
sense). We do not define that quantity here, which is simply the supremum of a map with values in
`α →ₘ[μ] β` (see measure_theory/ae_eq_fun.lean).

## Main definitions

* `ess_sup f μ := μ.ae.limsup f`
* `ess_inf f μ := μ.ae.liminf f`
-/

open MeasureTheory Filter TopologicalSpace

open ENNReal MeasureTheory

variable {α β : Type _} {m : MeasurableSpace α} {μ ν : Measure α}

section ConditionallyCompleteLattice

variable [ConditionallyCompleteLattice β]

/-- Essential supremum of `f` with respect to measure `μ`: the smallest `c : β` such that
`f x ≤ c` a.e. -/
def essSup {m : MeasurableSpace α} (f : α → β) (μ : Measure α) :=
  μ.ae.limsup f
#align ess_sup essSup

/-- Essential infimum of `f` with respect to measure `μ`: the greatest `c : β` such that
`c ≤ f x` a.e. -/
def essInf {m : MeasurableSpace α} (f : α → β) (μ : Measure α) :=
  μ.ae.liminf f
#align ess_inf essInf

theorem essSup_congr_ae {f g : α → β} (hfg : f =ᵐ[μ] g) : essSup f μ = essSup g μ :=
  limsup_congr hfg
#align ess_sup_congr_ae essSup_congr_ae

theorem essInf_congr_ae {f g : α → β} (hfg : f =ᵐ[μ] g) : essInf f μ = essInf g μ :=
  @essSup_congr_ae α βᵒᵈ _ _ _ _ _ hfg
#align ess_inf_congr_ae essInf_congr_ae

end ConditionallyCompleteLattice

section ConditionallyCompleteLinearOrder

variable [ConditionallyCompleteLinearOrder β]

theorem essSup_eq_infₛ {m : MeasurableSpace α} (μ : Measure α) (f : α → β) :
    essSup f μ = infₛ { a | μ { x | a < f x } = 0 } :=
  by
  dsimp [essSup, limsup, Limsup]
  congr
  ext a
  simp [eventually_map, ae_iff]
#align ess_sup_eq_Inf essSup_eq_infₛ

end ConditionallyCompleteLinearOrder

section CompleteLattice

variable [CompleteLattice β]

@[simp]
theorem essSup_measure_zero {m : MeasurableSpace α} {f : α → β} : essSup f (0 : Measure α) = ⊥ :=
  le_bot_iff.mp (infₛ_le (by simp [Set.mem_setOf_eq, eventually_le, ae_iff]))
#align ess_sup_measure_zero essSup_measure_zero

@[simp]
theorem essInf_measure_zero {m : MeasurableSpace α} {f : α → β} : essInf f (0 : Measure α) = ⊤ :=
  @essSup_measure_zero α βᵒᵈ _ _ _
#align ess_inf_measure_zero essInf_measure_zero

theorem essSup_mono_ae {f g : α → β} (hfg : f ≤ᵐ[μ] g) : essSup f μ ≤ essSup g μ :=
  limsup_le_limsup hfg
#align ess_sup_mono_ae essSup_mono_ae

theorem essInf_mono_ae {f g : α → β} (hfg : f ≤ᵐ[μ] g) : essInf f μ ≤ essInf g μ :=
  liminf_le_liminf hfg
#align ess_inf_mono_ae essInf_mono_ae

theorem essSup_const (c : β) (hμ : μ ≠ 0) : essSup (fun x : α => c) μ = c :=
  haveI hμ_ne_bot : μ.ae.ne_bot := by rwa [ne_bot_iff, Ne.def, ae_eq_bot]
  limsup_const c
#align ess_sup_const essSup_const

theorem essSup_le_of_ae_le {f : α → β} (c : β) (hf : f ≤ᵐ[μ] fun _ => c) : essSup f μ ≤ c :=
  by
  refine' (essSup_mono_ae hf).trans _
  by_cases hμ : μ = 0
  · simp [hμ]
  · rwa [essSup_const]
#align ess_sup_le_of_ae_le essSup_le_of_ae_le

theorem essInf_const (c : β) (hμ : μ ≠ 0) : essInf (fun x : α => c) μ = c :=
  @essSup_const α βᵒᵈ _ _ _ _ hμ
#align ess_inf_const essInf_const

theorem le_essInf_of_ae_le {f : α → β} (c : β) (hf : (fun _ => c) ≤ᵐ[μ] f) : c ≤ essInf f μ :=
  @essSup_le_of_ae_le α βᵒᵈ _ _ _ _ c hf
#align le_ess_inf_of_ae_le le_essInf_of_ae_le

theorem essSup_const_bot : essSup (fun x : α => (⊥ : β)) μ = (⊥ : β) :=
  limsup_const_bot
#align ess_sup_const_bot essSup_const_bot

theorem essInf_const_top : essInf (fun x : α => (⊤ : β)) μ = (⊤ : β) :=
  liminf_const_top
#align ess_inf_const_top essInf_const_top

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
theorem OrderIso.essSup_apply {m : MeasurableSpace α} {γ} [CompleteLattice γ] (f : α → β)
    (μ : Measure α) (g : β ≃o γ) : g (essSup f μ) = essSup (fun x => g (f x)) μ :=
  by
  refine' OrderIso.limsup_apply g _ _ _ _
  all_goals
    run_tac
      is_bounded_default
#align order_iso.ess_sup_apply OrderIso.essSup_apply

theorem OrderIso.essInf_apply {m : MeasurableSpace α} {γ} [CompleteLattice γ] (f : α → β)
    (μ : Measure α) (g : β ≃o γ) : g (essInf f μ) = essInf (fun x => g (f x)) μ :=
  @OrderIso.essSup_apply α βᵒᵈ _ _ γᵒᵈ _ _ _ g.dual
#align order_iso.ess_inf_apply OrderIso.essInf_apply

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
theorem essSup_mono_measure {f : α → β} (hμν : ν ≪ μ) : essSup f ν ≤ essSup f μ :=
  by
  refine' limsup_le_limsup_of_le (measure.ae_le_iff_absolutely_continuous.mpr hμν) _ _
  all_goals
    run_tac
      is_bounded_default
#align ess_sup_mono_measure essSup_mono_measure

theorem essSup_mono_measure' {α : Type _} {β : Type _} {m : MeasurableSpace α}
    {μ ν : MeasureTheory.Measure α} [CompleteLattice β] {f : α → β} (hμν : ν ≤ μ) :
    essSup f ν ≤ essSup f μ :=
  essSup_mono_measure (Measure.absolutelyContinuousOfLe hμν)
#align ess_sup_mono_measure' essSup_mono_measure'

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
theorem essInf_antitone_measure {f : α → β} (hμν : μ ≪ ν) : essInf f ν ≤ essInf f μ :=
  by
  refine' liminf_le_liminf_of_le (measure.ae_le_iff_absolutely_continuous.mpr hμν) _ _
  all_goals
    run_tac
      is_bounded_default
#align ess_inf_antitone_measure essInf_antitone_measure

theorem essSup_smul_measure {f : α → β} {c : ℝ≥0∞} (hc : c ≠ 0) : essSup f (c • μ) = essSup f μ :=
  by
  simp_rw [essSup]
  suffices h_smul : (c • μ).ae = μ.ae; · rw [h_smul]
  ext1
  simp_rw [mem_ae_iff]
  simp [hc]
#align ess_sup_smul_measure essSup_smul_measure

section TopologicalSpace

variable {γ : Type _} {mγ : MeasurableSpace γ} {f : α → γ} {g : γ → β}

include mγ

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
theorem essSup_comp_le_essSup_map_measure (hf : AeMeasurable f μ) :
    essSup (g ∘ f) μ ≤ essSup g (Measure.map f μ) :=
  by
  refine'
    Limsup_le_Limsup_of_le (fun t => _)
      (by
        run_tac
          is_bounded_default)
      (by
        run_tac
          is_bounded_default)
  simp_rw [Filter.mem_map]
  have : g ∘ f ⁻¹' t = f ⁻¹' (g ⁻¹' t) := by
    ext1 x
    simp_rw [Set.mem_preimage]
  rw [this]
  exact fun h => mem_ae_of_mem_ae_map hf h
#align ess_sup_comp_le_ess_sup_map_measure essSup_comp_le_essSup_map_measure

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
theorem MeasurableEmbedding.essSup_map_measure (hf : MeasurableEmbedding f) :
    essSup g (Measure.map f μ) = essSup (g ∘ f) μ :=
  by
  refine' le_antisymm _ (essSup_comp_le_essSup_map_measure hf.measurable.ae_measurable)
  refine'
    Limsup_le_Limsup
      (by
        run_tac
          is_bounded_default)
      (by
        run_tac
          is_bounded_default)
      fun c h_le => _
  rw [eventually_map] at h_le⊢
  exact hf.ae_map_iff.mpr h_le
#align measurable_embedding.ess_sup_map_measure MeasurableEmbedding.essSup_map_measure

variable [MeasurableSpace β] [TopologicalSpace β] [SecondCountableTopology β]
  [OrderClosedTopology β] [OpensMeasurableSpace β]

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
theorem essSup_map_measure_of_measurable (hg : Measurable g) (hf : AeMeasurable f μ) :
    essSup g (Measure.map f μ) = essSup (g ∘ f) μ :=
  by
  refine' le_antisymm _ (essSup_comp_le_essSup_map_measure hf)
  refine'
    Limsup_le_Limsup
      (by
        run_tac
          is_bounded_default)
      (by
        run_tac
          is_bounded_default)
      fun c h_le => _
  rw [eventually_map] at h_le⊢
  rw [ae_map_iff hf (measurableSet_le hg measurable_const)]
  exact h_le
#align ess_sup_map_measure_of_measurable essSup_map_measure_of_measurable

theorem essSup_map_measure (hg : AeMeasurable g (Measure.map f μ)) (hf : AeMeasurable f μ) :
    essSup g (Measure.map f μ) = essSup (g ∘ f) μ :=
  by
  rw [essSup_congr_ae hg.ae_eq_mk, essSup_map_measure_of_measurable hg.measurable_mk hf]
  refine' essSup_congr_ae _
  have h_eq := ae_of_ae_map hf hg.ae_eq_mk
  rw [← eventually_eq] at h_eq
  exact h_eq.symm
#align ess_sup_map_measure essSup_map_measure

omit mγ

end TopologicalSpace

end CompleteLattice

section CompleteLinearOrder

variable [CompleteLinearOrder β]

theorem ae_lt_of_essSup_lt {f : α → β} {x : β} (hf : essSup f μ < x) : ∀ᵐ y ∂μ, f y < x :=
  Filter.eventually_lt_of_limsup_lt hf
#align ae_lt_of_ess_sup_lt ae_lt_of_essSup_lt

theorem ae_lt_of_lt_essInf {f : α → β} {x : β} (hf : x < essInf f μ) : ∀ᵐ y ∂μ, x < f y :=
  @ae_lt_of_essSup_lt α βᵒᵈ _ _ _ _ _ hf
#align ae_lt_of_lt_ess_inf ae_lt_of_lt_essInf

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
theorem essSup_indicator_eq_essSup_restrict [Zero β] {s : Set α} {f : α → β}
    (hf : 0 ≤ᵐ[μ.restrict s] f) (hs : MeasurableSet s) (hs_not_null : μ s ≠ 0) :
    essSup (s.indicator f) μ = essSup f (μ.restrict s) :=
  by
  refine'
    le_antisymm _
      (Limsup_le_Limsup_of_le (map_restrict_ae_le_map_indicator_ae hs)
        (by
          run_tac
            is_bounded_default)
        (by
          run_tac
            is_bounded_default))
  refine'
    Limsup_le_Limsup
      (by
        run_tac
          is_bounded_default)
      (by
        run_tac
          is_bounded_default)
      fun c h_restrict_le => _
  rw [eventually_map] at h_restrict_le⊢
  rw [ae_restrict_iff' hs] at h_restrict_le
  have hc : 0 ≤ c := by
    rsuffices ⟨x, hx⟩ : ∃ x, 0 ≤ f x ∧ f x ≤ c
    exact hx.1.trans hx.2
    refine' frequently.exists _
    · exact μ.ae
    rw [eventually_le, ae_restrict_iff' hs] at hf
    have hs' : ∃ᵐ x ∂μ, x ∈ s := by
      contrapose! hs_not_null
      rw [not_frequently, ae_iff] at hs_not_null
      suffices { a : α | ¬a ∉ s } = s by rwa [← this]
      simp
    refine' hs'.mp (hf.mp (h_restrict_le.mono fun x hxs_imp_c hxf_nonneg hxs => _))
    rw [Pi.zero_apply] at hxf_nonneg
    exact ⟨hxf_nonneg hxs, hxs_imp_c hxs⟩
  refine' h_restrict_le.mono fun x hxc => _
  by_cases hxs : x ∈ s
  · simpa [hxs] using hxc hxs
  · simpa [hxs] using hc
#align ess_sup_indicator_eq_ess_sup_restrict essSup_indicator_eq_essSup_restrict

end CompleteLinearOrder

namespace ENNReal

variable {f : α → ℝ≥0∞}

theorem ae_le_essSup (f : α → ℝ≥0∞) : ∀ᵐ y ∂μ, f y ≤ essSup f μ :=
  eventually_le_limsup f
#align ennreal.ae_le_ess_sup ENNReal.ae_le_essSup

@[simp]
theorem essSup_eq_zero_iff : essSup f μ = 0 ↔ f =ᵐ[μ] 0 :=
  limsup_eq_zero_iff
#align ennreal.ess_sup_eq_zero_iff ENNReal.essSup_eq_zero_iff

theorem essSup_const_mul {a : ℝ≥0∞} : essSup (fun x : α => a * f x) μ = a * essSup f μ :=
  limsup_const_mul
#align ennreal.ess_sup_const_mul ENNReal.essSup_const_mul

theorem essSup_mul_le (f g : α → ℝ≥0∞) : essSup (f * g) μ ≤ essSup f μ * essSup g μ :=
  limsup_mul_le f g
#align ennreal.ess_sup_mul_le ENNReal.essSup_mul_le

theorem essSup_add_le (f g : α → ℝ≥0∞) : essSup (f + g) μ ≤ essSup f μ + essSup g μ :=
  limsup_add_le f g
#align ennreal.ess_sup_add_le ENNReal.essSup_add_le

theorem essSup_liminf_le {ι} [Countable ι] [LinearOrder ι] (f : ι → α → ℝ≥0∞) :
    essSup (fun x => atTop.liminf fun n => f n x) μ ≤
      atTop.liminf fun n => essSup (fun x => f n x) μ :=
  by
  simp_rw [essSup]
  exact ENNReal.limsup_liminf_le_liminf_limsup fun a b => f b a
#align ennreal.ess_sup_liminf_le ENNReal.essSup_liminf_le

end ENNReal


-- Imagen_inversa_de_la_diferencia.lean
-- Imagen inversa de la diferencia
-- José A. Alonso Jiménez
-- Sevilla, 18 de junio de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que
--    f ⁻¹' u \ f ⁻¹' v ⊆ f ⁻¹' (u \ v)
-- ----------------------------------------------------------------------

import data.set.basic

open set

variables {α : Type*} {β : Type*}
variable  f : α → β
variables u v : set β

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

example : f ⁻¹' u \ f ⁻¹' v ⊆ f ⁻¹' (u \ v) :=
begin
  intros x hx,
  rw mem_preimage,
  split,
  { rw ← mem_preimage,
    exact hx.1, },
  { rw ← mem_preimage,
    exact hx.2, },
end

-- 2ª demostración
-- ===============

example : f ⁻¹' u \ f ⁻¹' v ⊆ f ⁻¹' (u \ v) :=
begin
  intros x hx,
  split,
  { exact hx.1, },
  { exact hx.2, },
end

-- 3ª demostración
-- ===============

example : f ⁻¹' u \ f ⁻¹' v ⊆ f ⁻¹' (u \ v) :=
begin
  intros x hx,
  exact ⟨hx.1, hx.2⟩,
end

-- 4ª demostración
-- ===============

example : f ⁻¹' u \ f ⁻¹' v ⊆ f ⁻¹' (u \ v) :=
begin
  rintros x ⟨h1, h2⟩,
  exact ⟨h1, h2⟩,
end

-- 5ª demostración
-- ===============

example : f ⁻¹' u \ f ⁻¹' v ⊆ f ⁻¹' (u \ v) :=
subset.rfl

-- 6ª demostración
-- ===============

example : f ⁻¹' u \ f ⁻¹' v ⊆ f ⁻¹' (u \ v) :=
by finish


open nat

example : ∀ n m, n * succ m = n + n * m := begin
    intros,
    rw [mul_succ, add_comm]
end


import neater_proof.rings_and_properties
import neater_proof.natural_number_lemmas

local infixr ` + ` : 80 := plus
local infixr ` * ` : 80 := mult

/-- These definitions and results will be used when i come to prove that Ideal multiplication is
    associative. -/

/- A finite sum of elements of a Ring -/
def fin_sum {R : Ring} (f : ℕ → R.R) : ℕ → R.R
 | 0 := f 0
 | (nat.succ n) := f (nat.succ n) + fin_sum n

/- (material for) The sum of two such finite sums -/
def fin_sum_sum {R : Ring} (nx : ℕ) (fx fy : ℕ → R.R) : ℕ → R.R
 | 0 := fx 0
 | (nat.succ n) := if n < nx then (fx (nat.succ n) + fin_sum fx n)
                   else (fy (nat.succ n - nat.succ nx) + fin_sum_sum n)

/- The three definitions below allow us to show that finite sums can form ideals -/

def add_fun {R : Ring} (nx : ℕ) (fx fy : ℕ → R.R) : ℕ → R.R
 | 0 := fx 0
 | (nat.succ n) := if n < nx then fx (nat.succ n)
                   else fy (nat.succ n - nat.succ nx)

def mul_fun {R : Ring} (m : R.R) (f : ℕ → R.R) : ℕ → R.R :=
 λ n, m * (f n)

def mul_fun' {R : Ring} (m : R.R) (f : ℕ → R.R) : ℕ → R.R :=
 λ n, (f n) * m

namespace add_fin_sum

/- adding the first nx terms of such a sum of finite sums just produces the first such sum -/
lemma early_sum {R : Ring} (fx fy : ℕ → R.R) : ∀ n : ℕ, fin_sum_sum n fx fy n = fin_sum fx n :=
begin
 intro n,
 induction n with n hn,
 { exact rfl, },
 { have h : n < nat.succ n,
   { exact nat.lt_succ_self n, },
   have h' : fin_sum_sum (nat.succ n) fx fy (nat.succ n)
           = ite (n < nat.succ n) (fx (nat.succ n) + fin_sum fx n) -- 'ite' means 'if..then..else'
                 (fy (nat.succ n - nat.succ (nat.succ n)) + fin_sum_sum (nat.succ n) fx fy n),
   { exact rfl, },
   -- it's not very elegant, but this produces the equality desired by way of the defintion.
   -- This three-step method (prove the inequality, get to the ite by rfl and then use simp)
   -- will be used over and over again in later lemmas.
   have h'' : fin_sum_sum (nat.succ n) fx fy (nat.succ n) = fx (nat.succ n) + fin_sum fx n,
   { simp [h, h'], },
   exact h'', },
end

/- similar to the above -/
lemma early_sum' {R : Ring} (nx : ℕ) (fx fy : ℕ → R.R) :
∀ n : ℕ, n < nx → fin_sum_sum nx fx fy n = fin_sum fx n :=
begin
 intros n hn,
 induction n with n indh,
 { exact rfl, },
 { have h : n < nx,
   { apply nat.lt_of_succ_lt,
     exact hn, },
   specialize indh h,
   have h' : fin_sum_sum nx fx fy (nat.succ n)
             = ite (n < nx) (fx (nat.succ n) + fin_sum fx n)
                   (fy (nat.succ n - nat.succ nx) + fin_sum_sum nx fx fy n),
   { exact rfl, },
   have h'' : fin_sum_sum nx fx fy (nat.succ n) = fx (nat.succ n) + fin_sum fx n,
   { simp [h, h'], },
   calc
    fin_sum_sum nx fx fy (nat.succ n) = fx (nat.succ n) + fin_sum fx n : by exact h''
                                  ... = fin_sum fx (nat.succ n)        : by exact rfl, },
end

/- Showing that, given the right argument, the material for the sum of finite sums returns the
   value we want it to return.  -/
lemma sum_is_sum {R : Ring} (nx ny : ℕ) (fx fy : ℕ → R.R) :
(fin_sum_sum nx fx fy) (nat.succ (nat.add nx ny)) = (fin_sum fx nx) + (fin_sum fy ny) :=
begin
 induction ny with ny indhy,
 { have h : nat.add nx 0 = nx,
   { exact nat.add_zero nx, },
   rw h,
   have h' : fin_sum_sum nx fx fy (nat.succ nx)
           = ite (nx < nx) (fx (nat.succ nx) + fin_sum fx nx)
                           (fy (nat.succ nx - nat.succ nx) + fin_sum_sum nx fx fy nx),
   { exact rfl, },
   have h'' : ¬ nx < nx,
   { exact lt_irrfl nx },
   -- see, here is the three step method again!
   have h''' : fin_sum_sum nx fx fy (nat.succ nx)
             = fy (nat.succ nx - nat.succ nx) + fin_sum_sum nx fx fy nx,
   { simp [h', h''], },
   rw h''',
   have h'''' : nat.succ nx - nat.succ nx = 0,
   { exact nat.sub_self (nat.succ nx), },
   rw h'''',
   calc
    fy 0 + fin_sum_sum nx fx fy nx = fin_sum_sum nx fx fy nx  + fy 0 : by rw plus_comm
                               ... = fin_sum fx nx + fy 0            : by rw early_sum
                               ... = fin_sum fx nx + fin_sum fy 0    : by exact rfl, },
 { rw add_succ_commute nx ny,
   have h := succ_add_nlt nx ny,
   have h' : fin_sum_sum nx fx fy (nat.succ (nat.succ (nat.add nx ny)))
             = ite (nat.succ (nat.add nx ny) < nx)
               (fx (nat.succ (nat.succ (nat.add nx ny))) + fin_sum fx (nat.succ (nat.add nx ny)))
               (fy (nat.succ (nat.succ (nat.add nx ny)) - nat.succ nx)
               + fin_sum_sum nx fx fy (nat.succ (nat.add nx ny))),
   { exact rfl, },
   calc
    fin_sum_sum nx fx fy (nat.succ (nat.succ (nat.add nx ny)))
        = fy (nat.succ (nat.succ (nat.add nx ny)) - nat.succ nx)
          + fin_sum_sum nx fx fy (nat.succ (nat.add nx ny))      : by simp [h, h'] -- three step!
    ... = fy (nat.succ (nat.succ (nat.add nx ny)) - nat.succ nx) + fin_sum fx nx + fin_sum fy ny
                                                                 : by rw indhy
    ... = fy (nat.succ ny) + fin_sum fx nx + fin_sum fy ny       : by{ simp,
                                                                       rw succ_add_minus_is_succ }
    ... = fy (nat.succ ny) + fin_sum fy ny + fin_sum fx nx       : by rw plus_comm
                                                                      (fin_sum fx nx)
                                                                      (fin_sum fy ny)
    ... = (fy (nat.succ ny) + fin_sum fy ny) + fin_sum fx nx     : by rw plus_assoc
    ... = fin_sum fy (nat.succ ny) + fin_sum fx nx               : by exact rfl
    ... = fin_sum fx nx + fin_sum fy (nat.succ ny)               : by rw plus_comm, },
end

/- This is the key result in this section: that, for any two finite sums, their sum can be given
   as another finite sum -/
lemma add_fin_sum {R : Ring} (nx : ℕ) (fx fy : ℕ → R.R) :
∀ n : ℕ, fin_sum (add_fun nx fx fy) n = fin_sum_sum nx fx fy n :=
begin
 intro n,
 induction n with n indh,
 { exact rfl, },
 { have hn : n < nx ∨ ¬ n < nx,
   { exact lt_decidable nx n, },
   cases hn,
   { have h : fin_sum (add_fun nx fx fy) (nat.succ n)
              = add_fun nx fx fy (nat.succ n) + fin_sum (add_fun nx fx fy) n,
     { exact rfl, },
     have h' : add_fun nx fx fy (nat.succ n) = ite (n < nx) (fx (nat.succ n))
                                                   (fy (nat.succ n - nat.succ nx)),
     { exact rfl, },
     have h'' : add_fun nx fx fy (nat.succ n) = fx (nat.succ n),
     { simp [hn, h'] },
     have h''' : nat.succ n = nx ∨ nat.succ n < nx,
     { exact nat.eq_or_lt_of_le (nat.succ_le_of_lt hn), },
     calc
      fin_sum (add_fun nx fx fy) (nat.succ n)
          = add_fun nx fx fy (nat.succ n) + fin_sum (add_fun nx fx fy) n : by exact h
      ... = fx (nat.succ n) + fin_sum_sum nx fx fy n : by { rw h'', rw indh }
      ... = fx (nat.succ n) + fin_sum fx n           : by rw early_sum' nx fx fy n hn
      ... = fin_sum fx (nat.succ n)                  : by exact rfl
      ... = fin_sum_sum nx fx fy (nat.succ n)        : by { symmetry,
                                                            cases h''',
                                                            { rw h''',
                                                              exact early_sum fx fy nx, },
                                                            { exact early_sum'
                                                              nx fx fy (nat.succ n) h''' }, }, },
   -- it may not be entirely clear what's going on in the last line of the calc above, and that's
   -- my bad, but essentially all i'm doing is considering the two possible cases, where
   -- nat.succ n = nx and where nat.succ n < nx, and using the different forms of early_sum to
   -- prove the equality in each one of these cases in turn

   { have h : fin_sum (add_fun nx fx fy) (nat.succ n)
              = add_fun nx fx fy (nat.succ n) + fin_sum (add_fun nx fx fy) n,
     { exact rfl, },
     have h' : fin_sum_sum nx fx fy (nat.succ n)
              = ite (n < nx) (fx (nat.succ n) + fin_sum fx n)
                (fy (nat.succ n - nat.succ nx) + fin_sum_sum nx fx fy n),
     { exact rfl, },
     have h'' : fin_sum_sum nx fx fy (nat.succ n)
              = fy (nat.succ n -  nat.succ nx) + fin_sum_sum nx fx fy n,
     { simp [hn, h'], },
     have h''' : add_fun nx fx fy (nat.succ n) = ite (n < nx) (fx (nat.succ n))
                                                 (fy (nat.succ n - nat.succ nx)),
     { exact rfl, },
     have h'''' : add_fun nx fx fy (nat.succ n) = fy (nat.succ n - nat.succ nx),
     { simp [hn, h'''], },
     calc
      fin_sum (add_fun nx fx fy) (nat.succ n)
          = add_fun nx fx fy (nat.succ n) + fin_sum (add_fun nx fx fy) n : by exact h
      ... = fy (nat.succ n - nat.succ nx) + fin_sum (add_fun nx fx fy) n : by rw h''''
      ... = fy (nat.succ n - nat.succ nx) + fin_sum_sum nx fx fy n       : by rw indh
      ... = fin_sum_sum nx fx fy (nat.succ n)                            : by rw ← h'', }, },
end

end add_fin_sum

namespace mul_fin_sum

/- left multiples of finite sums are finite sums  -/
lemma mul_fin_sum {R : Ring} (y : R.R) (f : ℕ → R.R) :
∀ n : ℕ, y * (fin_sum f n) = fin_sum (mul_fun y f) n :=
begin
 intro n,
 induction n with n hn,
 { exact rfl, },
 { have h : fin_sum (mul_fun y f) (nat.succ n)
            = mul_fun y f (nat.succ n) + fin_sum (mul_fun y f) n,
   { exact rfl, },
   have h' : mul_fun y f (nat.succ n) = y * f (nat.succ n),
   { exact rfl, },
   symmetry,
   calc
    fin_sum (mul_fun y f) (nat.succ n)
        = mul_fun y f (nat.succ n) + fin_sum (mul_fun y f) n : by exact h
    ... = (y * f (nat.succ n)) + y * fin_sum f n : by { rw h', rw ← hn }
    ... = y * (f (nat.succ n) + fin_sum f n)     : by rw ← left_distributivity
    ... = y * fin_sum f (nat.succ n)             : by exact rfl, },
end

/- right multiples of finite sums are finite sums  -/
lemma mul_fin_sum' {R : Ring} (y : R.R) (f : ℕ → R.R) :
∀ n : ℕ, (fin_sum f n) * y = fin_sum (mul_fun' y f) n :=
begin
 intro n,
 induction n with n hn,
 { exact rfl, },
 { have h : fin_sum (mul_fun' y f) (nat.succ n)
           = mul_fun' y f (nat.succ n) + fin_sum (mul_fun' y f) n,
   { exact rfl, },
   have h' : mul_fun' y f (nat.succ n) = (f (nat.succ n)) * y,
   { exact rfl, },
   symmetry,
   calc
    fin_sum (mul_fun' y f) (nat.succ n)
        = mul_fun' y f (nat.succ n) + fin_sum (mul_fun' y f) n : by exact h
    ... = ((f (nat.succ n)) * y) + (fin_sum f n) * y : by { rw h', rw ← hn }
    ... = (f (nat.succ n) + fin_sum f n) * y         : by rw ← right_distributivity
    ... = (fin_sum f (nat.succ n)) * y : by exact rfl, },
end

end mul_fin_sum


inductive iszero : ℕ → Prop
| iszero_0 : iszero 0
| iszero_S : ∀ n, iszero (nat.succ n) → iszero n

example (n) : iszero n ↔ n = 0 := begin
    constructor,
    intro n_is_zero,
    induction n_is_zero, {
        refl
    }, {
       cases n_is_zero_ih,
    }, {
        intro n_is_zero,
        rw n_is_zero,
        apply iszero.iszero_0
    }
end