/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne, Benjamin Davidson
-/
import analysis.special_functions.exp_deriv

/-!
# Differentiability of trigonometric functions

## Main statements

The differentiability of the usual trigonometric functions is proved, and their derivatives are
computed.

## Tags

sin, cos, tan, angle
-/

noncomputable theory
open_locale classical topological_space filter
open set filter

namespace complex

/-- The complex sine function is everywhere strictly differentiable, with the derivative `cos x`. -/
lemma has_strict_deriv_at_sin (x : ℂ) : has_strict_deriv_at sin (cos x) x :=
begin
  simp only [cos, div_eq_mul_inv],
  convert ((((has_strict_deriv_at_id x).neg.mul_const I).cexp.sub
    ((has_strict_deriv_at_id x).mul_const I).cexp).mul_const I).mul_const (2:ℂ)⁻¹,
  simp only [function.comp, id],
  rw [sub_mul, mul_assoc, mul_assoc, I_mul_I, neg_one_mul, neg_neg, mul_one, one_mul, mul_assoc,
      I_mul_I, mul_neg_one, sub_neg_eq_add, add_comm]
end

/-- The complex sine function is everywhere differentiable, with the derivative `cos x`. -/
lemma has_deriv_at_sin (x : ℂ) : has_deriv_at sin (cos x) x :=
(has_strict_deriv_at_sin x).has_deriv_at

lemma cont_diff_sin {n} : cont_diff ℂ n sin :=
(((cont_diff_neg.mul cont_diff_const).cexp.sub
  (cont_diff_id.mul cont_diff_const).cexp).mul cont_diff_const).div_const

lemma differentiable_sin : differentiable ℂ sin :=
λx, (has_deriv_at_sin x).differentiable_at

lemma differentiable_at_sin {x : ℂ} : differentiable_at ℂ sin x :=
differentiable_sin x

@[simp] lemma deriv_sin : deriv sin = cos :=
funext $ λ x, (has_deriv_at_sin x).deriv

/-- The complex cosine function is everywhere strictly differentiable, with the derivative
`-sin x`. -/
lemma has_strict_deriv_at_cos (x : ℂ) : has_strict_deriv_at cos (-sin x) x :=
begin
  simp only [sin, div_eq_mul_inv, neg_mul_eq_neg_mul],
  convert (((has_strict_deriv_at_id x).mul_const I).cexp.add
    ((has_strict_deriv_at_id x).neg.mul_const I).cexp).mul_const (2:ℂ)⁻¹,
  simp only [function.comp, id],
  ring
end

/-- The complex cosine function is everywhere differentiable, with the derivative `-sin x`. -/
lemma has_deriv_at_cos (x : ℂ) : has_deriv_at cos (-sin x) x :=
(has_strict_deriv_at_cos x).has_deriv_at

lemma cont_diff_cos {n} : cont_diff ℂ n cos :=
((cont_diff_id.mul cont_diff_const).cexp.add
  (cont_diff_neg.mul cont_diff_const).cexp).div_const

lemma differentiable_cos : differentiable ℂ cos :=
λx, (has_deriv_at_cos x).differentiable_at

lemma differentiable_at_cos {x : ℂ} : differentiable_at ℂ cos x :=
differentiable_cos x

lemma deriv_cos {x : ℂ} : deriv cos x = -sin x :=
(has_deriv_at_cos x).deriv

@[simp] lemma deriv_cos' : deriv cos = (λ x, -sin x) :=
funext $ λ x, deriv_cos

/-- The complex hyperbolic sine function is everywhere strictly differentiable, with the derivative
`cosh x`. -/
lemma has_strict_deriv_at_sinh (x : ℂ) : has_strict_deriv_at sinh (cosh x) x :=
begin
  simp only [cosh, div_eq_mul_inv],
  convert ((has_strict_deriv_at_exp x).sub (has_strict_deriv_at_id x).neg.cexp).mul_const (2:ℂ)⁻¹,
  rw [id, mul_neg_one, sub_eq_add_neg, neg_neg]
end

/-- The complex hyperbolic sine function is everywhere differentiable, with the derivative
`cosh x`. -/
lemma has_deriv_at_sinh (x : ℂ) : has_deriv_at sinh (cosh x) x :=
(has_strict_deriv_at_sinh x).has_deriv_at

lemma cont_diff_sinh {n} : cont_diff ℂ n sinh :=
(cont_diff_exp.sub cont_diff_neg.cexp).div_const

lemma differentiable_sinh : differentiable ℂ sinh :=
λx, (has_deriv_at_sinh x).differentiable_at

lemma differentiable_at_sinh {x : ℂ} : differentiable_at ℂ sinh x :=
differentiable_sinh x

@[simp] lemma deriv_sinh : deriv sinh = cosh :=
funext $ λ x, (has_deriv_at_sinh x).deriv

/-- The complex hyperbolic cosine function is everywhere strictly differentiable, with the
derivative `sinh x`. -/
lemma has_strict_deriv_at_cosh (x : ℂ) : has_strict_deriv_at cosh (sinh x) x :=
begin
  simp only [sinh, div_eq_mul_inv],
  convert ((has_strict_deriv_at_exp x).add (has_strict_deriv_at_id x).neg.cexp).mul_const (2:ℂ)⁻¹,
  rw [id, mul_neg_one, sub_eq_add_neg]
end

/-- The complex hyperbolic cosine function is everywhere differentiable, with the derivative
`sinh x`. -/
lemma has_deriv_at_cosh (x : ℂ) : has_deriv_at cosh (sinh x) x :=
(has_strict_deriv_at_cosh x).has_deriv_at

lemma cont_diff_cosh {n} : cont_diff ℂ n cosh :=
(cont_diff_exp.add cont_diff_neg.cexp).div_const

lemma differentiable_cosh : differentiable ℂ cosh :=
λx, (has_deriv_at_cosh x).differentiable_at

lemma differentiable_at_cosh {x : ℂ} : differentiable_at ℂ cosh x :=
differentiable_cosh x

@[simp] lemma deriv_cosh : deriv cosh = sinh :=
funext $ λ x, (has_deriv_at_cosh x).deriv

end complex

section
/-! ### Simp lemmas for derivatives of `λ x, complex.cos (f x)` etc., `f : ℂ → ℂ` -/

variables {f : ℂ → ℂ} {f' x : ℂ} {s : set ℂ}

/-! #### `complex.cos` -/

lemma has_strict_deriv_at.ccos (hf : has_strict_deriv_at f f' x) :
  has_strict_deriv_at (λ x, complex.cos (f x)) (- complex.sin (f x) * f') x :=
(complex.has_strict_deriv_at_cos (f x)).comp x hf

lemma has_deriv_at.ccos (hf : has_deriv_at f f' x) :
  has_deriv_at (λ x, complex.cos (f x)) (- complex.sin (f x) * f') x :=
(complex.has_deriv_at_cos (f x)).comp x hf

lemma has_deriv_within_at.ccos (hf : has_deriv_within_at f f' s x) :
  has_deriv_within_at (λ x, complex.cos (f x)) (- complex.sin (f x) * f') s x :=
(complex.has_deriv_at_cos (f x)).comp_has_deriv_within_at x hf

lemma deriv_within_ccos (hf : differentiable_within_at ℂ f s x)
  (hxs : unique_diff_within_at ℂ s x) :
  deriv_within (λx, complex.cos (f x)) s x = - complex.sin (f x) * (deriv_within f s x) :=
hf.has_deriv_within_at.ccos.deriv_within hxs

@[simp] lemma deriv_ccos (hc : differentiable_at ℂ f x) :
  deriv (λx, complex.cos (f x)) x = - complex.sin (f x) * (deriv f x) :=
hc.has_deriv_at.ccos.deriv

/-! #### `complex.sin` -/

lemma has_strict_deriv_at.csin (hf : has_strict_deriv_at f f' x) :
  has_strict_deriv_at (λ x, complex.sin (f x)) (complex.cos (f x) * f') x :=
(complex.has_strict_deriv_at_sin (f x)).comp x hf

lemma has_deriv_at.csin (hf : has_deriv_at f f' x) :
  has_deriv_at (λ x, complex.sin (f x)) (complex.cos (f x) * f') x :=
(complex.has_deriv_at_sin (f x)).comp x hf

lemma has_deriv_within_at.csin (hf : has_deriv_within_at f f' s x) :
  has_deriv_within_at (λ x, complex.sin (f x)) (complex.cos (f x) * f') s x :=
(complex.has_deriv_at_sin (f x)).comp_has_deriv_within_at x hf

lemma deriv_within_csin (hf : differentiable_within_at ℂ f s x)
  (hxs : unique_diff_within_at ℂ s x) :
  deriv_within (λx, complex.sin (f x)) s x = complex.cos (f x) * (deriv_within f s x) :=
hf.has_deriv_within_at.csin.deriv_within hxs

@[simp] lemma deriv_csin (hc : differentiable_at ℂ f x) :
  deriv (λx, complex.sin (f x)) x = complex.cos (f x) * (deriv f x) :=
hc.has_deriv_at.csin.deriv

/-! #### `complex.cosh` -/

lemma has_strict_deriv_at.ccosh (hf : has_strict_deriv_at f f' x) :
  has_strict_deriv_at (λ x, complex.cosh (f x)) (complex.sinh (f x) * f') x :=
(complex.has_strict_deriv_at_cosh (f x)).comp x hf

lemma has_deriv_at.ccosh (hf : has_deriv_at f f' x) :
  has_deriv_at (λ x, complex.cosh (f x)) (complex.sinh (f x) * f') x :=
(complex.has_deriv_at_cosh (f x)).comp x hf

lemma has_deriv_within_at.ccosh (hf : has_deriv_within_at f f' s x) :
  has_deriv_within_at (λ x, complex.cosh (f x)) (complex.sinh (f x) * f') s x :=
(complex.has_deriv_at_cosh (f x)).comp_has_deriv_within_at x hf

lemma deriv_within_ccosh (hf : differentiable_within_at ℂ f s x)
  (hxs : unique_diff_within_at ℂ s x) :
  deriv_within (λx, complex.cosh (f x)) s x = complex.sinh (f x) * (deriv_within f s x) :=
hf.has_deriv_within_at.ccosh.deriv_within hxs

@[simp] lemma deriv_ccosh (hc : differentiable_at ℂ f x) :
  deriv (λx, complex.cosh (f x)) x = complex.sinh (f x) * (deriv f x) :=
hc.has_deriv_at.ccosh.deriv

/-! #### `complex.sinh` -/

lemma has_strict_deriv_at.csinh (hf : has_strict_deriv_at f f' x) :
  has_strict_deriv_at (λ x, complex.sinh (f x)) (complex.cosh (f x) * f') x :=
(complex.has_strict_deriv_at_sinh (f x)).comp x hf

lemma has_deriv_at.csinh (hf : has_deriv_at f f' x) :
  has_deriv_at (λ x, complex.sinh (f x)) (complex.cosh (f x) * f') x :=
(complex.has_deriv_at_sinh (f x)).comp x hf

lemma has_deriv_within_at.csinh (hf : has_deriv_within_at f f' s x) :
  has_deriv_within_at (λ x, complex.sinh (f x)) (complex.cosh (f x) * f') s x :=
(complex.has_deriv_at_sinh (f x)).comp_has_deriv_within_at x hf

lemma deriv_within_csinh (hf : differentiable_within_at ℂ f s x)
  (hxs : unique_diff_within_at ℂ s x) :
  deriv_within (λx, complex.sinh (f x)) s x = complex.cosh (f x) * (deriv_within f s x) :=
hf.has_deriv_within_at.csinh.deriv_within hxs

@[simp] lemma deriv_csinh (hc : differentiable_at ℂ f x) :
  deriv (λx, complex.sinh (f x)) x = complex.cosh (f x) * (deriv f x) :=
hc.has_deriv_at.csinh.deriv

end

section
/-! ### Simp lemmas for derivatives of `λ x, complex.cos (f x)` etc., `f : E → ℂ` -/

variables {E : Type*} [normed_group E] [normed_space ℂ E] {f : E → ℂ} {f' : E →L[ℂ] ℂ}
  {x : E} {s : set E}

/-! #### `complex.cos` -/

lemma has_strict_fderiv_at.ccos (hf : has_strict_fderiv_at f f' x) :
  has_strict_fderiv_at (λ x, complex.cos (f x)) (- complex.sin (f x) • f') x :=
(complex.has_strict_deriv_at_cos (f x)).comp_has_strict_fderiv_at x hf

lemma has_fderiv_at.ccos (hf : has_fderiv_at f f' x) :
  has_fderiv_at (λ x, complex.cos (f x)) (- complex.sin (f x) • f') x :=
(complex.has_deriv_at_cos (f x)).comp_has_fderiv_at x hf

lemma has_fderiv_within_at.ccos (hf : has_fderiv_within_at f f' s x) :
  has_fderiv_within_at (λ x, complex.cos (f x)) (- complex.sin (f x) • f') s x :=
(complex.has_deriv_at_cos (f x)).comp_has_fderiv_within_at x hf

lemma differentiable_within_at.ccos (hf : differentiable_within_at ℂ f s x) :
  differentiable_within_at ℂ (λ x, complex.cos (f x)) s x :=
hf.has_fderiv_within_at.ccos.differentiable_within_at

@[simp] lemma differentiable_at.ccos (hc : differentiable_at ℂ f x) :
  differentiable_at ℂ (λx, complex.cos (f x)) x :=
hc.has_fderiv_at.ccos.differentiable_at

lemma differentiable_on.ccos (hc : differentiable_on ℂ f s) :
  differentiable_on ℂ (λx, complex.cos (f x)) s :=
λx h, (hc x h).ccos

@[simp] lemma differentiable.ccos (hc : differentiable ℂ f) :
  differentiable ℂ (λx, complex.cos (f x)) :=
λx, (hc x).ccos

lemma fderiv_within_ccos (hf : differentiable_within_at ℂ f s x)
  (hxs : unique_diff_within_at ℂ s x) :
  fderiv_within ℂ (λx, complex.cos (f x)) s x = - complex.sin (f x) • (fderiv_within ℂ f s x) :=
hf.has_fderiv_within_at.ccos.fderiv_within hxs

@[simp] lemma fderiv_ccos (hc : differentiable_at ℂ f x) :
  fderiv ℂ (λx, complex.cos (f x)) x = - complex.sin (f x) • (fderiv ℂ f x) :=
hc.has_fderiv_at.ccos.fderiv

lemma cont_diff.ccos {n} (h : cont_diff ℂ n f) :
  cont_diff ℂ n (λ x, complex.cos (f x)) :=
complex.cont_diff_cos.comp h

lemma cont_diff_at.ccos {n} (hf : cont_diff_at ℂ n f x) :
  cont_diff_at ℂ n (λ x, complex.cos (f x)) x :=
complex.cont_diff_cos.cont_diff_at.comp x hf

lemma cont_diff_on.ccos {n} (hf : cont_diff_on ℂ n f s) :
  cont_diff_on ℂ n (λ x, complex.cos (f x)) s :=
complex.cont_diff_cos.comp_cont_diff_on  hf

lemma cont_diff_within_at.ccos {n} (hf : cont_diff_within_at ℂ n f s x) :
  cont_diff_within_at ℂ n (λ x, complex.cos (f x)) s x :=
complex.cont_diff_cos.cont_diff_at.comp_cont_diff_within_at x hf

/-! #### `complex.sin` -/

lemma has_strict_fderiv_at.csin (hf : has_strict_fderiv_at f f' x) :
  has_strict_fderiv_at (λ x, complex.sin (f x)) (complex.cos (f x) • f') x :=
(complex.has_strict_deriv_at_sin (f x)).comp_has_strict_fderiv_at x hf

lemma has_fderiv_at.csin (hf : has_fderiv_at f f' x) :
  has_fderiv_at (λ x, complex.sin (f x)) (complex.cos (f x) • f') x :=
(complex.has_deriv_at_sin (f x)).comp_has_fderiv_at x hf

lemma has_fderiv_within_at.csin (hf : has_fderiv_within_at f f' s x) :
  has_fderiv_within_at (λ x, complex.sin (f x)) (complex.cos (f x) • f') s x :=
(complex.has_deriv_at_sin (f x)).comp_has_fderiv_within_at x hf

lemma differentiable_within_at.csin (hf : differentiable_within_at ℂ f s x) :
  differentiable_within_at ℂ (λ x, complex.sin (f x)) s x :=
hf.has_fderiv_within_at.csin.differentiable_within_at

@[simp] lemma differentiable_at.csin (hc : differentiable_at ℂ f x) :
  differentiable_at ℂ (λx, complex.sin (f x)) x :=
hc.has_fderiv_at.csin.differentiable_at

lemma differentiable_on.csin (hc : differentiable_on ℂ f s) :
  differentiable_on ℂ (λx, complex.sin (f x)) s :=
λx h, (hc x h).csin

@[simp] lemma differentiable.csin (hc : differentiable ℂ f) :
  differentiable ℂ (λx, complex.sin (f x)) :=
λx, (hc x).csin

lemma fderiv_within_csin (hf : differentiable_within_at ℂ f s x)
  (hxs : unique_diff_within_at ℂ s x) :
  fderiv_within ℂ (λx, complex.sin (f x)) s x = complex.cos (f x) • (fderiv_within ℂ f s x) :=
hf.has_fderiv_within_at.csin.fderiv_within hxs

@[simp] lemma fderiv_csin (hc : differentiable_at ℂ f x) :
  fderiv ℂ (λx, complex.sin (f x)) x = complex.cos (f x) • (fderiv ℂ f x) :=
hc.has_fderiv_at.csin.fderiv

lemma cont_diff.csin {n} (h : cont_diff ℂ n f) :
  cont_diff ℂ n (λ x, complex.sin (f x)) :=
complex.cont_diff_sin.comp h

lemma cont_diff_at.csin {n} (hf : cont_diff_at ℂ n f x) :
  cont_diff_at ℂ n (λ x, complex.sin (f x)) x :=
complex.cont_diff_sin.cont_diff_at.comp x hf

lemma cont_diff_on.csin {n} (hf : cont_diff_on ℂ n f s) :
  cont_diff_on ℂ n (λ x, complex.sin (f x)) s :=
complex.cont_diff_sin.comp_cont_diff_on  hf

lemma cont_diff_within_at.csin {n} (hf : cont_diff_within_at ℂ n f s x) :
  cont_diff_within_at ℂ n (λ x, complex.sin (f x)) s x :=
complex.cont_diff_sin.cont_diff_at.comp_cont_diff_within_at x hf

/-! #### `complex.cosh` -/

lemma has_strict_fderiv_at.ccosh (hf : has_strict_fderiv_at f f' x) :
  has_strict_fderiv_at (λ x, complex.cosh (f x)) (complex.sinh (f x) • f') x :=
(complex.has_strict_deriv_at_cosh (f x)).comp_has_strict_fderiv_at x hf

lemma has_fderiv_at.ccosh (hf : has_fderiv_at f f' x) :
  has_fderiv_at (λ x, complex.cosh (f x)) (complex.sinh (f x) • f') x :=
(complex.has_deriv_at_cosh (f x)).comp_has_fderiv_at x hf

lemma has_fderiv_within_at.ccosh (hf : has_fderiv_within_at f f' s x) :
  has_fderiv_within_at (λ x, complex.cosh (f x)) (complex.sinh (f x) • f') s x :=
(complex.has_deriv_at_cosh (f x)).comp_has_fderiv_within_at x hf

lemma differentiable_within_at.ccosh (hf : differentiable_within_at ℂ f s x) :
  differentiable_within_at ℂ (λ x, complex.cosh (f x)) s x :=
hf.has_fderiv_within_at.ccosh.differentiable_within_at

@[simp] lemma differentiable_at.ccosh (hc : differentiable_at ℂ f x) :
  differentiable_at ℂ (λx, complex.cosh (f x)) x :=
hc.has_fderiv_at.ccosh.differentiable_at

lemma differentiable_on.ccosh (hc : differentiable_on ℂ f s) :
  differentiable_on ℂ (λx, complex.cosh (f x)) s :=
λx h, (hc x h).ccosh

@[simp] lemma differentiable.ccosh (hc : differentiable ℂ f) :
  differentiable ℂ (λx, complex.cosh (f x)) :=
λx, (hc x).ccosh

lemma fderiv_within_ccosh (hf : differentiable_within_at ℂ f s x)
  (hxs : unique_diff_within_at ℂ s x) :
  fderiv_within ℂ (λx, complex.cosh (f x)) s x = complex.sinh (f x) • (fderiv_within ℂ f s x) :=
hf.has_fderiv_within_at.ccosh.fderiv_within hxs

@[simp] lemma fderiv_ccosh (hc : differentiable_at ℂ f x) :
  fderiv ℂ (λx, complex.cosh (f x)) x = complex.sinh (f x) • (fderiv ℂ f x) :=
hc.has_fderiv_at.ccosh.fderiv

lemma cont_diff.ccosh {n} (h : cont_diff ℂ n f) :
  cont_diff ℂ n (λ x, complex.cosh (f x)) :=
complex.cont_diff_cosh.comp h

lemma cont_diff_at.ccosh {n} (hf : cont_diff_at ℂ n f x) :
  cont_diff_at ℂ n (λ x, complex.cosh (f x)) x :=
complex.cont_diff_cosh.cont_diff_at.comp x hf

lemma cont_diff_on.ccosh {n} (hf : cont_diff_on ℂ n f s) :
  cont_diff_on ℂ n (λ x, complex.cosh (f x)) s :=
complex.cont_diff_cosh.comp_cont_diff_on  hf

lemma cont_diff_within_at.ccosh {n} (hf : cont_diff_within_at ℂ n f s x) :
  cont_diff_within_at ℂ n (λ x, complex.cosh (f x)) s x :=
complex.cont_diff_cosh.cont_diff_at.comp_cont_diff_within_at x hf

/-! #### `complex.sinh` -/

lemma has_strict_fderiv_at.csinh (hf : has_strict_fderiv_at f f' x) :
  has_strict_fderiv_at (λ x, complex.sinh (f x)) (complex.cosh (f x) • f') x :=
(complex.has_strict_deriv_at_sinh (f x)).comp_has_strict_fderiv_at x hf

lemma has_fderiv_at.csinh (hf : has_fderiv_at f f' x) :
  has_fderiv_at (λ x, complex.sinh (f x)) (complex.cosh (f x) • f') x :=
(complex.has_deriv_at_sinh (f x)).comp_has_fderiv_at x hf

lemma has_fderiv_within_at.csinh (hf : has_fderiv_within_at f f' s x) :
  has_fderiv_within_at (λ x, complex.sinh (f x)) (complex.cosh (f x) • f') s x :=
(complex.has_deriv_at_sinh (f x)).comp_has_fderiv_within_at x hf

lemma differentiable_within_at.csinh (hf : differentiable_within_at ℂ f s x) :
  differentiable_within_at ℂ (λ x, complex.sinh (f x)) s x :=
hf.has_fderiv_within_at.csinh.differentiable_within_at

@[simp] lemma differentiable_at.csinh (hc : differentiable_at ℂ f x) :
  differentiable_at ℂ (λx, complex.sinh (f x)) x :=
hc.has_fderiv_at.csinh.differentiable_at

lemma differentiable_on.csinh (hc : differentiable_on ℂ f s) :
  differentiable_on ℂ (λx, complex.sinh (f x)) s :=
λx h, (hc x h).csinh

@[simp] lemma differentiable.csinh (hc : differentiable ℂ f) :
  differentiable ℂ (λx, complex.sinh (f x)) :=
λx, (hc x).csinh

lemma fderiv_within_csinh (hf : differentiable_within_at ℂ f s x)
  (hxs : unique_diff_within_at ℂ s x) :
  fderiv_within ℂ (λx, complex.sinh (f x)) s x = complex.cosh (f x) • (fderiv_within ℂ f s x) :=
hf.has_fderiv_within_at.csinh.fderiv_within hxs

@[simp] lemma fderiv_csinh (hc : differentiable_at ℂ f x) :
  fderiv ℂ (λx, complex.sinh (f x)) x = complex.cosh (f x) • (fderiv ℂ f x) :=
hc.has_fderiv_at.csinh.fderiv

lemma cont_diff.csinh {n} (h : cont_diff ℂ n f) :
  cont_diff ℂ n (λ x, complex.sinh (f x)) :=
complex.cont_diff_sinh.comp h

lemma cont_diff_at.csinh {n} (hf : cont_diff_at ℂ n f x) :
  cont_diff_at ℂ n (λ x, complex.sinh (f x)) x :=
complex.cont_diff_sinh.cont_diff_at.comp x hf

lemma cont_diff_on.csinh {n} (hf : cont_diff_on ℂ n f s) :
  cont_diff_on ℂ n (λ x, complex.sinh (f x)) s :=
complex.cont_diff_sinh.comp_cont_diff_on  hf

lemma cont_diff_within_at.csinh {n} (hf : cont_diff_within_at ℂ n f s x) :
  cont_diff_within_at ℂ n (λ x, complex.sinh (f x)) s x :=
complex.cont_diff_sinh.cont_diff_at.comp_cont_diff_within_at x hf

end

namespace real

variables {x y z : ℝ}

lemma has_strict_deriv_at_sin (x : ℝ) : has_strict_deriv_at sin (cos x) x :=
(complex.has_strict_deriv_at_sin x).real_of_complex

lemma has_deriv_at_sin (x : ℝ) : has_deriv_at sin (cos x) x :=
(has_strict_deriv_at_sin x).has_deriv_at

lemma cont_diff_sin {n} : cont_diff ℝ n sin :=
complex.cont_diff_sin.real_of_complex

lemma differentiable_sin : differentiable ℝ sin :=
λx, (has_deriv_at_sin x).differentiable_at

lemma differentiable_at_sin : differentiable_at ℝ sin x :=
differentiable_sin x

@[simp] lemma deriv_sin : deriv sin = cos :=
funext $ λ x, (has_deriv_at_sin x).deriv

lemma has_strict_deriv_at_cos (x : ℝ) : has_strict_deriv_at cos (-sin x) x :=
(complex.has_strict_deriv_at_cos x).real_of_complex

lemma has_deriv_at_cos (x : ℝ) : has_deriv_at cos (-sin x) x :=
(complex.has_deriv_at_cos x).real_of_complex

lemma cont_diff_cos {n} : cont_diff ℝ n cos :=
complex.cont_diff_cos.real_of_complex

lemma differentiable_cos : differentiable ℝ cos :=
λx, (has_deriv_at_cos x).differentiable_at

lemma differentiable_at_cos : differentiable_at ℝ cos x :=
differentiable_cos x

lemma deriv_cos : deriv cos x = - sin x :=
(has_deriv_at_cos x).deriv

@[simp] lemma deriv_cos' : deriv cos = (λ x, - sin x) :=
funext $ λ _, deriv_cos

lemma has_strict_deriv_at_sinh (x : ℝ) : has_strict_deriv_at sinh (cosh x) x :=
(complex.has_strict_deriv_at_sinh x).real_of_complex

lemma has_deriv_at_sinh (x : ℝ) : has_deriv_at sinh (cosh x) x :=
(complex.has_deriv_at_sinh x).real_of_complex

lemma cont_diff_sinh {n} : cont_diff ℝ n sinh :=
complex.cont_diff_sinh.real_of_complex

lemma differentiable_sinh : differentiable ℝ sinh :=
λx, (has_deriv_at_sinh x).differentiable_at

lemma differentiable_at_sinh : differentiable_at ℝ sinh x :=
differentiable_sinh x

@[simp] lemma deriv_sinh : deriv sinh = cosh :=
funext $ λ x, (has_deriv_at_sinh x).deriv

lemma has_strict_deriv_at_cosh (x : ℝ) : has_strict_deriv_at cosh (sinh x) x :=
(complex.has_strict_deriv_at_cosh x).real_of_complex

lemma has_deriv_at_cosh (x : ℝ) : has_deriv_at cosh (sinh x) x :=
(complex.has_deriv_at_cosh x).real_of_complex

lemma cont_diff_cosh {n} : cont_diff ℝ n cosh :=
complex.cont_diff_cosh.real_of_complex

lemma differentiable_cosh : differentiable ℝ cosh :=
λx, (has_deriv_at_cosh x).differentiable_at

lemma differentiable_at_cosh : differentiable_at ℝ cosh x :=
differentiable_cosh x

@[simp] lemma deriv_cosh : deriv cosh = sinh :=
funext $ λ x, (has_deriv_at_cosh x).deriv

/-- `sinh` is strictly monotone. -/
lemma sinh_strict_mono : strict_mono sinh :=
strict_mono_of_deriv_pos $ by { rw real.deriv_sinh, exact cosh_pos }

end real

section
/-! ### Simp lemmas for derivatives of `λ x, real.cos (f x)` etc., `f : ℝ → ℝ` -/

variables {f : ℝ → ℝ} {f' x : ℝ} {s : set ℝ}

/-! #### `real.cos` -/

lemma has_strict_deriv_at.cos (hf : has_strict_deriv_at f f' x) :
  has_strict_deriv_at (λ x, real.cos (f x)) (- real.sin (f x) * f') x :=
(real.has_strict_deriv_at_cos (f x)).comp x hf

lemma has_deriv_at.cos (hf : has_deriv_at f f' x) :
  has_deriv_at (λ x, real.cos (f x)) (- real.sin (f x) * f') x :=
(real.has_deriv_at_cos (f x)).comp x hf

lemma has_deriv_within_at.cos (hf : has_deriv_within_at f f' s x) :
  has_deriv_within_at (λ x, real.cos (f x)) (- real.sin (f x) * f') s x :=
(real.has_deriv_at_cos (f x)).comp_has_deriv_within_at x hf

lemma deriv_within_cos (hf : differentiable_within_at ℝ f s x)
  (hxs : unique_diff_within_at ℝ s x) :
  deriv_within (λx, real.cos (f x)) s x = - real.sin (f x) * (deriv_within f s x) :=
hf.has_deriv_within_at.cos.deriv_within hxs

@[simp] lemma deriv_cos (hc : differentiable_at ℝ f x) :
  deriv (λx, real.cos (f x)) x = - real.sin (f x) * (deriv f x) :=
hc.has_deriv_at.cos.deriv

/-! #### `real.sin` -/

lemma has_strict_deriv_at.sin (hf : has_strict_deriv_at f f' x) :
  has_strict_deriv_at (λ x, real.sin (f x)) (real.cos (f x) * f') x :=
(real.has_strict_deriv_at_sin (f x)).comp x hf

lemma has_deriv_at.sin (hf : has_deriv_at f f' x) :
  has_deriv_at (λ x, real.sin (f x)) (real.cos (f x) * f') x :=
(real.has_deriv_at_sin (f x)).comp x hf

lemma has_deriv_within_at.sin (hf : has_deriv_within_at f f' s x) :
  has_deriv_within_at (λ x, real.sin (f x)) (real.cos (f x) * f') s x :=
(real.has_deriv_at_sin (f x)).comp_has_deriv_within_at x hf

lemma deriv_within_sin (hf : differentiable_within_at ℝ f s x)
  (hxs : unique_diff_within_at ℝ s x) :
  deriv_within (λx, real.sin (f x)) s x = real.cos (f x) * (deriv_within f s x) :=
hf.has_deriv_within_at.sin.deriv_within hxs

@[simp] lemma deriv_sin (hc : differentiable_at ℝ f x) :
  deriv (λx, real.sin (f x)) x = real.cos (f x) * (deriv f x) :=
hc.has_deriv_at.sin.deriv

/-! #### `real.cosh` -/

lemma has_strict_deriv_at.cosh (hf : has_strict_deriv_at f f' x) :
  has_strict_deriv_at (λ x, real.cosh (f x)) (real.sinh (f x) * f') x :=
(real.has_strict_deriv_at_cosh (f x)).comp x hf

lemma has_deriv_at.cosh (hf : has_deriv_at f f' x) :
  has_deriv_at (λ x, real.cosh (f x)) (real.sinh (f x) * f') x :=
(real.has_deriv_at_cosh (f x)).comp x hf

lemma has_deriv_within_at.cosh (hf : has_deriv_within_at f f' s x) :
  has_deriv_within_at (λ x, real.cosh (f x)) (real.sinh (f x) * f') s x :=
(real.has_deriv_at_cosh (f x)).comp_has_deriv_within_at x hf

lemma deriv_within_cosh (hf : differentiable_within_at ℝ f s x)
  (hxs : unique_diff_within_at ℝ s x) :
  deriv_within (λx, real.cosh (f x)) s x = real.sinh (f x) * (deriv_within f s x) :=
hf.has_deriv_within_at.cosh.deriv_within hxs

@[simp] lemma deriv_cosh (hc : differentiable_at ℝ f x) :
  deriv (λx, real.cosh (f x)) x = real.sinh (f x) * (deriv f x) :=
hc.has_deriv_at.cosh.deriv

/-! #### `real.sinh` -/

lemma has_strict_deriv_at.sinh (hf : has_strict_deriv_at f f' x) :
  has_strict_deriv_at (λ x, real.sinh (f x)) (real.cosh (f x) * f') x :=
(real.has_strict_deriv_at_sinh (f x)).comp x hf

lemma has_deriv_at.sinh (hf : has_deriv_at f f' x) :
  has_deriv_at (λ x, real.sinh (f x)) (real.cosh (f x) * f') x :=
(real.has_deriv_at_sinh (f x)).comp x hf

lemma has_deriv_within_at.sinh (hf : has_deriv_within_at f f' s x) :
  has_deriv_within_at (λ x, real.sinh (f x)) (real.cosh (f x) * f') s x :=
(real.has_deriv_at_sinh (f x)).comp_has_deriv_within_at x hf

lemma deriv_within_sinh (hf : differentiable_within_at ℝ f s x)
  (hxs : unique_diff_within_at ℝ s x) :
  deriv_within (λx, real.sinh (f x)) s x = real.cosh (f x) * (deriv_within f s x) :=
hf.has_deriv_within_at.sinh.deriv_within hxs

@[simp] lemma deriv_sinh (hc : differentiable_at ℝ f x) :
  deriv (λx, real.sinh (f x)) x = real.cosh (f x) * (deriv f x) :=
hc.has_deriv_at.sinh.deriv

end

section

/-! ### Simp lemmas for derivatives of `λ x, real.cos (f x)` etc., `f : E → ℝ` -/

variables {E : Type*} [normed_group E] [normed_space ℝ E] {f : E → ℝ} {f' : E →L[ℝ] ℝ}
  {x : E} {s : set E}

/-! #### `real.cos` -/

lemma has_strict_fderiv_at.cos (hf : has_strict_fderiv_at f f' x) :
  has_strict_fderiv_at (λ x, real.cos (f x)) (- real.sin (f x) • f') x :=
(real.has_strict_deriv_at_cos (f x)).comp_has_strict_fderiv_at x hf

lemma has_fderiv_at.cos (hf : has_fderiv_at f f' x) :
  has_fderiv_at (λ x, real.cos (f x)) (- real.sin (f x) • f') x :=
(real.has_deriv_at_cos (f x)).comp_has_fderiv_at x hf

lemma has_fderiv_within_at.cos (hf : has_fderiv_within_at f f' s x) :
  has_fderiv_within_at (λ x, real.cos (f x)) (- real.sin (f x) • f') s x :=
(real.has_deriv_at_cos (f x)).comp_has_fderiv_within_at x hf

lemma differentiable_within_at.cos (hf : differentiable_within_at ℝ f s x) :
  differentiable_within_at ℝ (λ x, real.cos (f x)) s x :=
hf.has_fderiv_within_at.cos.differentiable_within_at

@[simp] lemma differentiable_at.cos (hc : differentiable_at ℝ f x) :
  differentiable_at ℝ (λx, real.cos (f x)) x :=
hc.has_fderiv_at.cos.differentiable_at

lemma differentiable_on.cos (hc : differentiable_on ℝ f s) :
  differentiable_on ℝ (λx, real.cos (f x)) s :=
λx h, (hc x h).cos

@[simp] lemma differentiable.cos (hc : differentiable ℝ f) :
  differentiable ℝ (λx, real.cos (f x)) :=
λx, (hc x).cos

lemma fderiv_within_cos (hf : differentiable_within_at ℝ f s x)
  (hxs : unique_diff_within_at ℝ s x) :
  fderiv_within ℝ (λx, real.cos (f x)) s x = - real.sin (f x) • (fderiv_within ℝ f s x) :=
hf.has_fderiv_within_at.cos.fderiv_within hxs

@[simp] lemma fderiv_cos (hc : differentiable_at ℝ f x) :
  fderiv ℝ (λx, real.cos (f x)) x = - real.sin (f x) • (fderiv ℝ f x) :=
hc.has_fderiv_at.cos.fderiv

lemma cont_diff.cos {n} (h : cont_diff ℝ n f) :
  cont_diff ℝ n (λ x, real.cos (f x)) :=
real.cont_diff_cos.comp h

lemma cont_diff_at.cos {n} (hf : cont_diff_at ℝ n f x) :
  cont_diff_at ℝ n (λ x, real.cos (f x)) x :=
real.cont_diff_cos.cont_diff_at.comp x hf

lemma cont_diff_on.cos {n} (hf : cont_diff_on ℝ n f s) :
  cont_diff_on ℝ n (λ x, real.cos (f x)) s :=
real.cont_diff_cos.comp_cont_diff_on  hf

lemma cont_diff_within_at.cos {n} (hf : cont_diff_within_at ℝ n f s x) :
  cont_diff_within_at ℝ n (λ x, real.cos (f x)) s x :=
real.cont_diff_cos.cont_diff_at.comp_cont_diff_within_at x hf

/-! #### `real.sin` -/

lemma has_strict_fderiv_at.sin (hf : has_strict_fderiv_at f f' x) :
  has_strict_fderiv_at (λ x, real.sin (f x)) (real.cos (f x) • f') x :=
(real.has_strict_deriv_at_sin (f x)).comp_has_strict_fderiv_at x hf

lemma has_fderiv_at.sin (hf : has_fderiv_at f f' x) :
  has_fderiv_at (λ x, real.sin (f x)) (real.cos (f x) • f') x :=
(real.has_deriv_at_sin (f x)).comp_has_fderiv_at x hf

lemma has_fderiv_within_at.sin (hf : has_fderiv_within_at f f' s x) :
  has_fderiv_within_at (λ x, real.sin (f x)) (real.cos (f x) • f') s x :=
(real.has_deriv_at_sin (f x)).comp_has_fderiv_within_at x hf

lemma differentiable_within_at.sin (hf : differentiable_within_at ℝ f s x) :
  differentiable_within_at ℝ (λ x, real.sin (f x)) s x :=
hf.has_fderiv_within_at.sin.differentiable_within_at

@[simp] lemma differentiable_at.sin (hc : differentiable_at ℝ f x) :
  differentiable_at ℝ (λx, real.sin (f x)) x :=
hc.has_fderiv_at.sin.differentiable_at

lemma differentiable_on.sin (hc : differentiable_on ℝ f s) :
  differentiable_on ℝ (λx, real.sin (f x)) s :=
λx h, (hc x h).sin

@[simp] lemma differentiable.sin (hc : differentiable ℝ f) :
  differentiable ℝ (λx, real.sin (f x)) :=
λx, (hc x).sin

lemma fderiv_within_sin (hf : differentiable_within_at ℝ f s x)
  (hxs : unique_diff_within_at ℝ s x) :
  fderiv_within ℝ (λx, real.sin (f x)) s x = real.cos (f x) • (fderiv_within ℝ f s x) :=
hf.has_fderiv_within_at.sin.fderiv_within hxs

@[simp] lemma fderiv_sin (hc : differentiable_at ℝ f x) :
  fderiv ℝ (λx, real.sin (f x)) x = real.cos (f x) • (fderiv ℝ f x) :=
hc.has_fderiv_at.sin.fderiv

lemma cont_diff.sin {n} (h : cont_diff ℝ n f) :
  cont_diff ℝ n (λ x, real.sin (f x)) :=
real.cont_diff_sin.comp h

lemma cont_diff_at.sin {n} (hf : cont_diff_at ℝ n f x) :
  cont_diff_at ℝ n (λ x, real.sin (f x)) x :=
real.cont_diff_sin.cont_diff_at.comp x hf

lemma cont_diff_on.sin {n} (hf : cont_diff_on ℝ n f s) :
  cont_diff_on ℝ n (λ x, real.sin (f x)) s :=
real.cont_diff_sin.comp_cont_diff_on  hf

lemma cont_diff_within_at.sin {n} (hf : cont_diff_within_at ℝ n f s x) :
  cont_diff_within_at ℝ n (λ x, real.sin (f x)) s x :=
real.cont_diff_sin.cont_diff_at.comp_cont_diff_within_at x hf

/-! #### `real.cosh` -/

lemma has_strict_fderiv_at.cosh (hf : has_strict_fderiv_at f f' x) :
  has_strict_fderiv_at (λ x, real.cosh (f x)) (real.sinh (f x) • f') x :=
(real.has_strict_deriv_at_cosh (f x)).comp_has_strict_fderiv_at x hf

lemma has_fderiv_at.cosh (hf : has_fderiv_at f f' x) :
  has_fderiv_at (λ x, real.cosh (f x)) (real.sinh (f x) • f') x :=
(real.has_deriv_at_cosh (f x)).comp_has_fderiv_at x hf

lemma has_fderiv_within_at.cosh (hf : has_fderiv_within_at f f' s x) :
  has_fderiv_within_at (λ x, real.cosh (f x)) (real.sinh (f x) • f') s x :=
(real.has_deriv_at_cosh (f x)).comp_has_fderiv_within_at x hf

lemma differentiable_within_at.cosh (hf : differentiable_within_at ℝ f s x) :
  differentiable_within_at ℝ (λ x, real.cosh (f x)) s x :=
hf.has_fderiv_within_at.cosh.differentiable_within_at

@[simp] lemma differentiable_at.cosh (hc : differentiable_at ℝ f x) :
  differentiable_at ℝ (λx, real.cosh (f x)) x :=
hc.has_fderiv_at.cosh.differentiable_at

lemma differentiable_on.cosh (hc : differentiable_on ℝ f s) :
  differentiable_on ℝ (λx, real.cosh (f x)) s :=
λx h, (hc x h).cosh

@[simp] lemma differentiable.cosh (hc : differentiable ℝ f) :
  differentiable ℝ (λx, real.cosh (f x)) :=
λx, (hc x).cosh

lemma fderiv_within_cosh (hf : differentiable_within_at ℝ f s x)
  (hxs : unique_diff_within_at ℝ s x) :
  fderiv_within ℝ (λx, real.cosh (f x)) s x = real.sinh (f x) • (fderiv_within ℝ f s x) :=
hf.has_fderiv_within_at.cosh.fderiv_within hxs

@[simp] lemma fderiv_cosh (hc : differentiable_at ℝ f x) :
  fderiv ℝ (λx, real.cosh (f x)) x = real.sinh (f x) • (fderiv ℝ f x) :=
hc.has_fderiv_at.cosh.fderiv

lemma cont_diff.cosh {n} (h : cont_diff ℝ n f) :
  cont_diff ℝ n (λ x, real.cosh (f x)) :=
real.cont_diff_cosh.comp h

lemma cont_diff_at.cosh {n} (hf : cont_diff_at ℝ n f x) :
  cont_diff_at ℝ n (λ x, real.cosh (f x)) x :=
real.cont_diff_cosh.cont_diff_at.comp x hf

lemma cont_diff_on.cosh {n} (hf : cont_diff_on ℝ n f s) :
  cont_diff_on ℝ n (λ x, real.cosh (f x)) s :=
real.cont_diff_cosh.comp_cont_diff_on  hf

lemma cont_diff_within_at.cosh {n} (hf : cont_diff_within_at ℝ n f s x) :
  cont_diff_within_at ℝ n (λ x, real.cosh (f x)) s x :=
real.cont_diff_cosh.cont_diff_at.comp_cont_diff_within_at x hf

/-! #### `real.sinh` -/

lemma has_strict_fderiv_at.sinh (hf : has_strict_fderiv_at f f' x) :
  has_strict_fderiv_at (λ x, real.sinh (f x)) (real.cosh (f x) • f') x :=
(real.has_strict_deriv_at_sinh (f x)).comp_has_strict_fderiv_at x hf

lemma has_fderiv_at.sinh (hf : has_fderiv_at f f' x) :
  has_fderiv_at (λ x, real.sinh (f x)) (real.cosh (f x) • f') x :=
(real.has_deriv_at_sinh (f x)).comp_has_fderiv_at x hf

lemma has_fderiv_within_at.sinh (hf : has_fderiv_within_at f f' s x) :
  has_fderiv_within_at (λ x, real.sinh (f x)) (real.cosh (f x) • f') s x :=
(real.has_deriv_at_sinh (f x)).comp_has_fderiv_within_at x hf

lemma differentiable_within_at.sinh (hf : differentiable_within_at ℝ f s x) :
  differentiable_within_at ℝ (λ x, real.sinh (f x)) s x :=
hf.has_fderiv_within_at.sinh.differentiable_within_at

@[simp] lemma differentiable_at.sinh (hc : differentiable_at ℝ f x) :
  differentiable_at ℝ (λx, real.sinh (f x)) x :=
hc.has_fderiv_at.sinh.differentiable_at

lemma differentiable_on.sinh (hc : differentiable_on ℝ f s) :
  differentiable_on ℝ (λx, real.sinh (f x)) s :=
λx h, (hc x h).sinh

@[simp] lemma differentiable.sinh (hc : differentiable ℝ f) :
  differentiable ℝ (λx, real.sinh (f x)) :=
λx, (hc x).sinh

lemma fderiv_within_sinh (hf : differentiable_within_at ℝ f s x)
  (hxs : unique_diff_within_at ℝ s x) :
  fderiv_within ℝ (λx, real.sinh (f x)) s x = real.cosh (f x) • (fderiv_within ℝ f s x) :=
hf.has_fderiv_within_at.sinh.fderiv_within hxs

@[simp] lemma fderiv_sinh (hc : differentiable_at ℝ f x) :
  fderiv ℝ (λx, real.sinh (f x)) x = real.cosh (f x) • (fderiv ℝ f x) :=
hc.has_fderiv_at.sinh.fderiv

lemma cont_diff.sinh {n} (h : cont_diff ℝ n f) :
  cont_diff ℝ n (λ x, real.sinh (f x)) :=
real.cont_diff_sinh.comp h

lemma cont_diff_at.sinh {n} (hf : cont_diff_at ℝ n f x) :
  cont_diff_at ℝ n (λ x, real.sinh (f x)) x :=
real.cont_diff_sinh.cont_diff_at.comp x hf

lemma cont_diff_on.sinh {n} (hf : cont_diff_on ℝ n f s) :
  cont_diff_on ℝ n (λ x, real.sinh (f x)) s :=
real.cont_diff_sinh.comp_cont_diff_on  hf

lemma cont_diff_within_at.sinh {n} (hf : cont_diff_within_at ℝ n f s x) :
  cont_diff_within_at ℝ n (λ x, real.sinh (f x)) s x :=
real.cont_diff_sinh.cont_diff_at.comp_cont_diff_within_at x hf

end


/-
Copyright (c) 2020 Fox Thomson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Fox Thomson
-/
import set_theory.pgame

/-!
# Basic definitions about who has a winning stratergy

We define `G.first_loses`, `G.first_wins`, `G.left_wins` and `G.right_wins` for a pgame `G`, which
means the second, first, left and right players have a winning strategy respectively.
These are defined by inequalities which can be unfolded with `pgame.lt_def` and `pgame.le_def`.
-/

namespace pgame

local infix ` ≈ ` := equiv

/-- The player who goes first loses -/
def first_loses (G : pgame) : Prop := G ≤ 0 ∧ 0 ≤ G

/-- The player who goes first wins -/
def first_wins (G : pgame) : Prop := 0 < G ∧ G < 0

/-- The left player can always win -/
def left_wins (G : pgame) : Prop := 0 < G ∧ 0 ≤ G

/-- The right player can always win -/
def right_wins (G : pgame) : Prop := G ≤ 0 ∧ G < 0

theorem zero_first_loses : first_loses 0 := by tidy
theorem one_left_wins : left_wins 1 :=
⟨by { rw lt_def_le, tidy }, by rw le_def; tidy⟩

theorem star_first_wins : first_wins star := ⟨zero_lt_star, star_lt_zero⟩
theorem omega_left_wins : left_wins omega :=
⟨by { rw lt_def_le, exact or.inl ⟨ulift.up 0, by tidy⟩ }, by rw le_def; tidy⟩

lemma winner_cases (G : pgame) : G.left_wins ∨ G.right_wins ∨ G.first_loses ∨ G.first_wins :=
begin
  classical,
  by_cases hpos : 0 < G;
  by_cases hneg : G < 0;
  { try { rw not_lt at hpos },
    try { rw not_lt at hneg },
    try { left, exact ⟨hpos, hneg⟩ },
    try { right, left, exact ⟨hpos, hneg⟩ },
    try { right, right, left, exact ⟨hpos, hneg⟩ },
    try { right, right, right, exact ⟨hpos, hneg⟩ } }
end

lemma first_loses_is_zero {G : pgame} : G.first_loses ↔ G ≈ 0 := by refl

lemma first_loses_of_equiv {G H : pgame} (h : G ≈ H) : G.first_loses → H.first_loses :=
λ hGp, ⟨le_of_equiv_of_le h.symm hGp.1, le_of_le_of_equiv hGp.2 h⟩
lemma first_wins_of_equiv {G H : pgame} (h : G ≈ H) : G.first_wins → H.first_wins :=
λ hGn, ⟨lt_of_lt_of_equiv hGn.1 h, lt_of_equiv_of_lt h.symm hGn.2⟩
lemma left_wins_of_equiv {G H : pgame} (h : G ≈ H) : G.left_wins → H.left_wins :=
λ hGl, ⟨lt_of_lt_of_equiv hGl.1 h, le_of_le_of_equiv hGl.2 h⟩
lemma right_wins_of_equiv {G H : pgame} (h : G ≈ H) : G.right_wins → H.right_wins :=
λ hGr, ⟨le_of_equiv_of_le h.symm hGr.1, lt_of_equiv_of_lt h.symm hGr.2⟩

lemma first_loses_of_equiv_iff {G H : pgame} (h : G ≈ H) : G.first_loses ↔ H.first_loses :=
⟨first_loses_of_equiv h, first_loses_of_equiv h.symm⟩
lemma first_wins_of_equiv_iff {G H : pgame} (h : G ≈ H) : G.first_wins ↔ H.first_wins :=
⟨first_wins_of_equiv h, first_wins_of_equiv h.symm⟩
lemma left_wins_of_equiv_iff {G H : pgame} (h : G ≈ H) : G.left_wins ↔ H.left_wins :=
⟨left_wins_of_equiv h, left_wins_of_equiv h.symm⟩
lemma right_wins_of_equiv_iff {G H : pgame} (h : G ≈ H) : G.right_wins ↔ H.right_wins :=
⟨right_wins_of_equiv h, right_wins_of_equiv h.symm⟩

lemma not_first_wins_of_first_loses {G : pgame} : G.first_loses → ¬G.first_wins :=
begin
  rw first_loses_is_zero,
  rintros h ⟨h₀, -⟩,
  exact lt_irrefl 0 (lt_of_lt_of_equiv h₀ h)
end

lemma not_first_loses_of_first_wins {G : pgame} : G.first_wins → ¬G.first_loses :=
imp_not_comm.1 $ not_first_wins_of_first_loses

end pgame


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Yury Kudryashov
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.set.intervals.basic
import Mathlib.algebra.ordered_group
import Mathlib.PostPort

universes u u_1 w v

namespace Mathlib

/-!

# Upper / lower bounds

In this file we define:

* `upper_bounds`, `lower_bounds` : the set of upper bounds (resp., lower bounds) of a set;
* `bdd_above s`, `bdd_below s` : the set `s` is bounded above (resp., below), i.e., the set of upper
  (resp., lower) bounds of `s` is nonempty;
* `is_least s a`, `is_greatest s a` : `a` is a least (resp., greatest) element of `s`;
  for a partial order, it is unique if exists;
* `is_lub s a`, `is_glb s a` : `a` is a least upper bound (resp., a greatest lower bound)
  of `s`; for a partial order, it is unique if exists.

We also prove various lemmas about monotonicity, behaviour under `∪`, `∩`, `insert`, and provide
formulas for `∅`, `univ`, and intervals.
-/

/-!
### Definitions
-/

/-- The set of upper bounds of a set. -/
/-- The set of lower bounds of a set. -/
def upper_bounds {α : Type u} [preorder α] (s : set α) : set α :=
  set_of fun (x : α) => ∀ {a : α}, a ∈ s → a ≤ x

def lower_bounds {α : Type u} [preorder α] (s : set α) : set α :=
  set_of fun (x : α) => ∀ {a : α}, a ∈ s → x ≤ a

/-- A set is bounded above if there exists an upper bound. -/
/-- A set is bounded below if there exists a lower bound. -/
def bdd_above {α : Type u} [preorder α] (s : set α) := set.nonempty (upper_bounds s)

def bdd_below {α : Type u} [preorder α] (s : set α) := set.nonempty (lower_bounds s)

/-- `a` is a least element of a set `s`; for a partial order, it is unique if exists. -/
/-- `a` is a greatest element of a set `s`; for a partial order, it is unique if exists -/
def is_least {α : Type u} [preorder α] (s : set α) (a : α) := a ∈ s ∧ a ∈ lower_bounds s

def is_greatest {α : Type u} [preorder α] (s : set α) (a : α) := a ∈ s ∧ a ∈ upper_bounds s

/-- `a` is a least upper bound of a set `s`; for a partial order, it is unique if exists. -/
/-- `a` is a greatest lower bound of a set `s`; for a partial order, it is unique if exists. -/
def is_lub {α : Type u} [preorder α] (s : set α) : α → Prop := is_least (upper_bounds s)

def is_glb {α : Type u} [preorder α] (s : set α) : α → Prop := is_greatest (lower_bounds s)

theorem mem_upper_bounds {α : Type u} [preorder α] {s : set α} {a : α} :
    a ∈ upper_bounds s ↔ ∀ (x : α), x ∈ s → x ≤ a :=
  iff.rfl

theorem mem_lower_bounds {α : Type u} [preorder α] {s : set α} {a : α} :
    a ∈ lower_bounds s ↔ ∀ (x : α), x ∈ s → a ≤ x :=
  iff.rfl

/-- A set `s` is not bounded above if and only if for each `x` there exists `y ∈ s` such that `x`
is not greater than or equal to `y`. This version only assumes `preorder` structure and uses
`¬(y ≤ x)`. A version for linear orders is called `not_bdd_above_iff`. -/
theorem not_bdd_above_iff' {α : Type u} [preorder α] {s : set α} :
    ¬bdd_above s ↔ ∀ (x : α), ∃ (y : α), ∃ (H : y ∈ s), ¬y ≤ x :=
  sorry

/-- A set `s` is not bounded below if and only if for each `x` there exists `y ∈ s` such that `x`
is not less than or equal to `y`. This version only assumes `preorder` structure and uses
`¬(x ≤ y)`. A version for linear orders is called `not_bdd_below_iff`. -/
theorem not_bdd_below_iff' {α : Type u} [preorder α] {s : set α} :
    ¬bdd_below s ↔ ∀ (x : α), ∃ (y : α), ∃ (H : y ∈ s), ¬x ≤ y :=
  not_bdd_above_iff'

/-- A set `s` is not bounded above if and only if for each `x` there exists `y ∈ s` that is greater
than `x`. A version for preorders is called `not_bdd_above_iff'`. -/
theorem not_bdd_above_iff {α : Type u_1} [linear_order α] {s : set α} :
    ¬bdd_above s ↔ ∀ (x : α), ∃ (y : α), ∃ (H : y ∈ s), x < y :=
  sorry

/-- A set `s` is not bounded below if and only if for each `x` there exists `y ∈ s` that is less
than `x`. A version for preorders is called `not_bdd_below_iff'`. -/
theorem not_bdd_below_iff {α : Type u_1} [linear_order α] {s : set α} :
    ¬bdd_below s ↔ ∀ (x : α), ∃ (y : α), ∃ (H : y ∈ s), y < x :=
  not_bdd_above_iff

/-!
### Monotonicity
-/

theorem upper_bounds_mono_set {α : Type u} [preorder α] {s : set α} {t : set α} (hst : s ⊆ t) :
    upper_bounds t ⊆ upper_bounds s :=
  fun (b : α) (hb : b ∈ upper_bounds t) (x : α) (h : x ∈ s) => hb (hst h)

theorem lower_bounds_mono_set {α : Type u} [preorder α] {s : set α} {t : set α} (hst : s ⊆ t) :
    lower_bounds t ⊆ lower_bounds s :=
  fun (b : α) (hb : b ∈ lower_bounds t) (x : α) (h : x ∈ s) => hb (hst h)

theorem upper_bounds_mono_mem {α : Type u} [preorder α] {s : set α} {a : α} {b : α} (hab : a ≤ b) :
    a ∈ upper_bounds s → b ∈ upper_bounds s :=
  fun (ha : a ∈ upper_bounds s) (x : α) (h : x ∈ s) => le_trans (ha h) hab

theorem lower_bounds_mono_mem {α : Type u} [preorder α] {s : set α} {a : α} {b : α} (hab : a ≤ b) :
    b ∈ lower_bounds s → a ∈ lower_bounds s :=
  fun (hb : b ∈ lower_bounds s) (x : α) (h : x ∈ s) => le_trans hab (hb h)

theorem upper_bounds_mono {α : Type u} [preorder α] {s : set α} {t : set α} (hst : s ⊆ t) {a : α}
    {b : α} (hab : a ≤ b) : a ∈ upper_bounds t → b ∈ upper_bounds s :=
  fun (ha : a ∈ upper_bounds t) => upper_bounds_mono_set hst (upper_bounds_mono_mem hab ha)

theorem lower_bounds_mono {α : Type u} [preorder α] {s : set α} {t : set α} (hst : s ⊆ t) {a : α}
    {b : α} (hab : a ≤ b) : b ∈ lower_bounds t → a ∈ lower_bounds s :=
  fun (hb : b ∈ lower_bounds t) => lower_bounds_mono_set hst (lower_bounds_mono_mem hab hb)

/-- If `s ⊆ t` and `t` is bounded above, then so is `s`. -/
theorem bdd_above.mono {α : Type u} [preorder α] {s : set α} {t : set α} (h : s ⊆ t) :
    bdd_above t → bdd_above s :=
  set.nonempty.mono (upper_bounds_mono_set h)

/-- If `s ⊆ t` and `t` is bounded below, then so is `s`. -/
theorem bdd_below.mono {α : Type u} [preorder α] {s : set α} {t : set α} (h : s ⊆ t) :
    bdd_below t → bdd_below s :=
  set.nonempty.mono (lower_bounds_mono_set h)

/-- If `a` is a least upper bound for sets `s` and `p`, then it is a least upper bound for any
set `t`, `s ⊆ t ⊆ p`. -/
theorem is_lub.of_subset_of_superset {α : Type u} [preorder α] {a : α} {s : set α} {t : set α}
    {p : set α} (hs : is_lub s a) (hp : is_lub p a) (hst : s ⊆ t) (htp : t ⊆ p) : is_lub t a :=
  { left := upper_bounds_mono_set htp (and.left hp),
    right := lower_bounds_mono_set (upper_bounds_mono_set hst) (and.right hs) }

/-- If `a` is a greatest lower bound for sets `s` and `p`, then it is a greater lower bound for any
set `t`, `s ⊆ t ⊆ p`. -/
theorem is_glb.of_subset_of_superset {α : Type u} [preorder α] {a : α} {s : set α} {t : set α}
    {p : set α} (hs : is_glb s a) (hp : is_glb p a) (hst : s ⊆ t) (htp : t ⊆ p) : is_glb t a :=
  is_lub.of_subset_of_superset hs hp hst htp

theorem is_least.mono {α : Type u} [preorder α] {s : set α} {t : set α} {a : α} {b : α}
    (ha : is_least s a) (hb : is_least t b) (hst : s ⊆ t) : b ≤ a :=
  and.right hb a (hst (and.left ha))

theorem is_greatest.mono {α : Type u} [preorder α] {s : set α} {t : set α} {a : α} {b : α}
    (ha : is_greatest s a) (hb : is_greatest t b) (hst : s ⊆ t) : a ≤ b :=
  and.right hb a (hst (and.left ha))

theorem is_lub.mono {α : Type u} [preorder α] {s : set α} {t : set α} {a : α} {b : α}
    (ha : is_lub s a) (hb : is_lub t b) (hst : s ⊆ t) : a ≤ b :=
  is_least.mono hb ha (upper_bounds_mono_set hst)

theorem is_glb.mono {α : Type u} [preorder α] {s : set α} {t : set α} {a : α} {b : α}
    (ha : is_glb s a) (hb : is_glb t b) (hst : s ⊆ t) : b ≤ a :=
  is_greatest.mono hb ha (lower_bounds_mono_set hst)

/-!
### Conversions
-/

theorem is_least.is_glb {α : Type u} [preorder α] {s : set α} {a : α} (h : is_least s a) :
    is_glb s a :=
  { left := and.right h, right := fun (b : α) (hb : b ∈ lower_bounds s) => hb (and.left h) }

theorem is_greatest.is_lub {α : Type u} [preorder α] {s : set α} {a : α} (h : is_greatest s a) :
    is_lub s a :=
  { left := and.right h, right := fun (b : α) (hb : b ∈ upper_bounds s) => hb (and.left h) }

theorem is_lub.upper_bounds_eq {α : Type u} [preorder α] {s : set α} {a : α} (h : is_lub s a) :
    upper_bounds s = set.Ici a :=
  sorry

theorem is_glb.lower_bounds_eq {α : Type u} [preorder α] {s : set α} {a : α} (h : is_glb s a) :
    lower_bounds s = set.Iic a :=
  is_lub.upper_bounds_eq h

theorem is_least.lower_bounds_eq {α : Type u} [preorder α] {s : set α} {a : α} (h : is_least s a) :
    lower_bounds s = set.Iic a :=
  is_glb.lower_bounds_eq (is_least.is_glb h)

theorem is_greatest.upper_bounds_eq {α : Type u} [preorder α] {s : set α} {a : α}
    (h : is_greatest s a) : upper_bounds s = set.Ici a :=
  is_lub.upper_bounds_eq (is_greatest.is_lub h)

theorem is_lub_le_iff {α : Type u} [preorder α] {s : set α} {a : α} {b : α} (h : is_lub s a) :
    a ≤ b ↔ b ∈ upper_bounds s :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a ≤ b ↔ b ∈ upper_bounds s)) (is_lub.upper_bounds_eq h)))
    (iff.refl (a ≤ b))

theorem le_is_glb_iff {α : Type u} [preorder α] {s : set α} {a : α} {b : α} (h : is_glb s a) :
    b ≤ a ↔ b ∈ lower_bounds s :=
  eq.mpr (id (Eq._oldrec (Eq.refl (b ≤ a ↔ b ∈ lower_bounds s)) (is_glb.lower_bounds_eq h)))
    (iff.refl (b ≤ a))

/-- If `s` has a least upper bound, then it is bounded above. -/
theorem is_lub.bdd_above {α : Type u} [preorder α] {s : set α} {a : α} (h : is_lub s a) :
    bdd_above s :=
  Exists.intro a (and.left h)

/-- If `s` has a greatest lower bound, then it is bounded below. -/
theorem is_glb.bdd_below {α : Type u} [preorder α] {s : set α} {a : α} (h : is_glb s a) :
    bdd_below s :=
  Exists.intro a (and.left h)

/-- If `s` has a greatest element, then it is bounded above. -/
theorem is_greatest.bdd_above {α : Type u} [preorder α] {s : set α} {a : α} (h : is_greatest s a) :
    bdd_above s :=
  Exists.intro a (and.right h)

/-- If `s` has a least element, then it is bounded below. -/
theorem is_least.bdd_below {α : Type u} [preorder α] {s : set α} {a : α} (h : is_least s a) :
    bdd_below s :=
  Exists.intro a (and.right h)

theorem is_least.nonempty {α : Type u} [preorder α] {s : set α} {a : α} (h : is_least s a) :
    set.nonempty s :=
  Exists.intro a (and.left h)

theorem is_greatest.nonempty {α : Type u} [preorder α] {s : set α} {a : α} (h : is_greatest s a) :
    set.nonempty s :=
  Exists.intro a (and.left h)

/-!
### Union and intersection
-/

@[simp] theorem upper_bounds_union {α : Type u} [preorder α] {s : set α} {t : set α} :
    upper_bounds (s ∪ t) = upper_bounds s ∩ upper_bounds t :=
  sorry

@[simp] theorem lower_bounds_union {α : Type u} [preorder α] {s : set α} {t : set α} :
    lower_bounds (s ∪ t) = lower_bounds s ∩ lower_bounds t :=
  upper_bounds_union

theorem union_upper_bounds_subset_upper_bounds_inter {α : Type u} [preorder α] {s : set α}
    {t : set α} : upper_bounds s ∪ upper_bounds t ⊆ upper_bounds (s ∩ t) :=
  set.union_subset (upper_bounds_mono_set (set.inter_subset_left s t))
    (upper_bounds_mono_set (set.inter_subset_right s t))

theorem union_lower_bounds_subset_lower_bounds_inter {α : Type u} [preorder α] {s : set α}
    {t : set α} : lower_bounds s ∪ lower_bounds t ⊆ lower_bounds (s ∩ t) :=
  union_upper_bounds_subset_upper_bounds_inter

theorem is_least_union_iff {α : Type u} [preorder α] {a : α} {s : set α} {t : set α} :
    is_least (s ∪ t) a ↔ is_least s a ∧ a ∈ lower_bounds t ∨ a ∈ lower_bounds s ∧ is_least t a :=
  sorry

theorem is_greatest_union_iff {α : Type u} [preorder α] {s : set α} {t : set α} {a : α} :
    is_greatest (s ∪ t) a ↔
        is_greatest s a ∧ a ∈ upper_bounds t ∨ a ∈ upper_bounds s ∧ is_greatest t a :=
  is_least_union_iff

/-- If `s` is bounded, then so is `s ∩ t` -/
theorem bdd_above.inter_of_left {α : Type u} [preorder α] {s : set α} {t : set α}
    (h : bdd_above s) : bdd_above (s ∩ t) :=
  bdd_above.mono (set.inter_subset_left s t) h

/-- If `t` is bounded, then so is `s ∩ t` -/
theorem bdd_above.inter_of_right {α : Type u} [preorder α] {s : set α} {t : set α}
    (h : bdd_above t) : bdd_above (s ∩ t) :=
  bdd_above.mono (set.inter_subset_right s t) h

/-- If `s` is bounded, then so is `s ∩ t` -/
theorem bdd_below.inter_of_left {α : Type u} [preorder α] {s : set α} {t : set α}
    (h : bdd_below s) : bdd_below (s ∩ t) :=
  bdd_below.mono (set.inter_subset_left s t) h

/-- If `t` is bounded, then so is `s ∩ t` -/
theorem bdd_below.inter_of_right {α : Type u} [preorder α] {s : set α} {t : set α}
    (h : bdd_below t) : bdd_below (s ∩ t) :=
  bdd_below.mono (set.inter_subset_right s t) h

/-- If `s` and `t` are bounded above sets in a `semilattice_sup`, then so is `s ∪ t`. -/
theorem bdd_above.union {γ : Type w} [semilattice_sup γ] {s : set γ} {t : set γ} :
    bdd_above s → bdd_above t → bdd_above (s ∪ t) :=
  sorry

/-- The union of two sets is bounded above if and only if each of the sets is. -/
theorem bdd_above_union {γ : Type w} [semilattice_sup γ] {s : set γ} {t : set γ} :
    bdd_above (s ∪ t) ↔ bdd_above s ∧ bdd_above t :=
  sorry

theorem bdd_below.union {γ : Type w} [semilattice_inf γ] {s : set γ} {t : set γ} :
    bdd_below s → bdd_below t → bdd_below (s ∪ t) :=
  bdd_above.union

/--The union of two sets is bounded above if and only if each of the sets is.-/
theorem bdd_below_union {γ : Type w} [semilattice_inf γ] {s : set γ} {t : set γ} :
    bdd_below (s ∪ t) ↔ bdd_below s ∧ bdd_below t :=
  bdd_above_union

/-- If `a` is the least upper bound of `s` and `b` is the least upper bound of `t`,
then `a ⊔ b` is the least upper bound of `s ∪ t`. -/
theorem is_lub.union {γ : Type w} [semilattice_sup γ] {a : γ} {b : γ} {s : set γ} {t : set γ}
    (hs : is_lub s a) (ht : is_lub t b) : is_lub (s ∪ t) (a ⊔ b) :=
  sorry

/-- If `a` is the greatest lower bound of `s` and `b` is the greatest lower bound of `t`,
then `a ⊓ b` is the greatest lower bound of `s ∪ t`. -/
theorem is_glb.union {γ : Type w} [semilattice_inf γ] {a₁ : γ} {a₂ : γ} {s : set γ} {t : set γ}
    (hs : is_glb s a₁) (ht : is_glb t a₂) : is_glb (s ∪ t) (a₁ ⊓ a₂) :=
  is_lub.union hs ht

/-- If `a` is the least element of `s` and `b` is the least element of `t`,
then `min a b` is the least element of `s ∪ t`. -/
theorem is_least.union {γ : Type w} [linear_order γ] {a : γ} {b : γ} {s : set γ} {t : set γ}
    (ha : is_least s a) (hb : is_least t b) : is_least (s ∪ t) (min a b) :=
  sorry

/-- If `a` is the greatest element of `s` and `b` is the greatest element of `t`,
then `max a b` is the greatest element of `s ∪ t`. -/
theorem is_greatest.union {γ : Type w} [linear_order γ] {a : γ} {b : γ} {s : set γ} {t : set γ}
    (ha : is_greatest s a) (hb : is_greatest t b) : is_greatest (s ∪ t) (max a b) :=
  sorry

/-!
### Specific sets

#### Unbounded intervals
-/

theorem is_least_Ici {α : Type u} [preorder α] {a : α} : is_least (set.Ici a) a :=
  { left := set.left_mem_Ici, right := fun (x : α) => id }

theorem is_greatest_Iic {α : Type u} [preorder α] {a : α} : is_greatest (set.Iic a) a :=
  { left := set.right_mem_Iic, right := fun (x : α) => id }

theorem is_lub_Iic {α : Type u} [preorder α] {a : α} : is_lub (set.Iic a) a :=
  is_greatest.is_lub is_greatest_Iic

theorem is_glb_Ici {α : Type u} [preorder α] {a : α} : is_glb (set.Ici a) a :=
  is_least.is_glb is_least_Ici

theorem upper_bounds_Iic {α : Type u} [preorder α] {a : α} : upper_bounds (set.Iic a) = set.Ici a :=
  is_lub.upper_bounds_eq is_lub_Iic

theorem lower_bounds_Ici {α : Type u} [preorder α] {a : α} : lower_bounds (set.Ici a) = set.Iic a :=
  is_glb.lower_bounds_eq is_glb_Ici

theorem bdd_above_Iic {α : Type u} [preorder α] {a : α} : bdd_above (set.Iic a) :=
  is_lub.bdd_above is_lub_Iic

theorem bdd_below_Ici {α : Type u} [preorder α] {a : α} : bdd_below (set.Ici a) :=
  is_glb.bdd_below is_glb_Ici

theorem bdd_above_Iio {α : Type u} [preorder α] {a : α} : bdd_above (set.Iio a) :=
  Exists.intro a fun (x : α) (hx : x ∈ set.Iio a) => le_of_lt hx

theorem bdd_below_Ioi {α : Type u} [preorder α] {a : α} : bdd_below (set.Ioi a) :=
  Exists.intro a fun (x : α) (hx : x ∈ set.Ioi a) => le_of_lt hx

theorem is_lub_Iio {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} :
    is_lub (set.Iio a) a :=
  { left := fun (x : γ) (hx : x ∈ set.Iio a) => le_of_lt hx,
    right := fun (y : γ) (hy : y ∈ upper_bounds (set.Iio a)) => le_of_forall_ge_of_dense hy }

theorem is_glb_Ioi {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} :
    is_glb (set.Ioi a) a :=
  is_lub_Iio

theorem upper_bounds_Iio {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} :
    upper_bounds (set.Iio a) = set.Ici a :=
  is_lub.upper_bounds_eq is_lub_Iio

theorem lower_bounds_Ioi {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} :
    lower_bounds (set.Ioi a) = set.Iic a :=
  is_glb.lower_bounds_eq is_glb_Ioi

/-!
#### Singleton
-/

theorem is_greatest_singleton {α : Type u} [preorder α] {a : α} : is_greatest (singleton a) a :=
  { left := set.mem_singleton a,
    right := fun (x : α) (hx : x ∈ singleton a) => le_of_eq (set.eq_of_mem_singleton hx) }

theorem is_least_singleton {α : Type u} [preorder α] {a : α} : is_least (singleton a) a :=
  is_greatest_singleton

theorem is_lub_singleton {α : Type u} [preorder α] {a : α} : is_lub (singleton a) a :=
  is_greatest.is_lub is_greatest_singleton

theorem is_glb_singleton {α : Type u} [preorder α] {a : α} : is_glb (singleton a) a :=
  is_least.is_glb is_least_singleton

theorem bdd_above_singleton {α : Type u} [preorder α] {a : α} : bdd_above (singleton a) :=
  is_lub.bdd_above is_lub_singleton

theorem bdd_below_singleton {α : Type u} [preorder α] {a : α} : bdd_below (singleton a) :=
  is_glb.bdd_below is_glb_singleton

@[simp] theorem upper_bounds_singleton {α : Type u} [preorder α] {a : α} :
    upper_bounds (singleton a) = set.Ici a :=
  is_lub.upper_bounds_eq is_lub_singleton

@[simp] theorem lower_bounds_singleton {α : Type u} [preorder α] {a : α} :
    lower_bounds (singleton a) = set.Iic a :=
  is_glb.lower_bounds_eq is_glb_singleton

/-!
#### Bounded intervals
-/

theorem bdd_above_Icc {α : Type u} [preorder α] {a : α} {b : α} : bdd_above (set.Icc a b) :=
  Exists.intro b fun (_x : α) => and.right

theorem bdd_below_Icc {α : Type u} [preorder α] {a : α} {b : α} : bdd_below (set.Icc a b) :=
  Exists.intro a fun (_x : α) => and.left

theorem bdd_above_Ico {α : Type u} [preorder α] {a : α} {b : α} : bdd_above (set.Ico a b) :=
  bdd_above.mono set.Ico_subset_Icc_self bdd_above_Icc

theorem bdd_below_Ico {α : Type u} [preorder α] {a : α} {b : α} : bdd_below (set.Ico a b) :=
  bdd_below.mono set.Ico_subset_Icc_self bdd_below_Icc

theorem bdd_above_Ioc {α : Type u} [preorder α] {a : α} {b : α} : bdd_above (set.Ioc a b) :=
  bdd_above.mono set.Ioc_subset_Icc_self bdd_above_Icc

theorem bdd_below_Ioc {α : Type u} [preorder α] {a : α} {b : α} : bdd_below (set.Ioc a b) :=
  bdd_below.mono set.Ioc_subset_Icc_self bdd_below_Icc

theorem bdd_above_Ioo {α : Type u} [preorder α] {a : α} {b : α} : bdd_above (set.Ioo a b) :=
  bdd_above.mono set.Ioo_subset_Icc_self bdd_above_Icc

theorem bdd_below_Ioo {α : Type u} [preorder α] {a : α} {b : α} : bdd_below (set.Ioo a b) :=
  bdd_below.mono set.Ioo_subset_Icc_self bdd_below_Icc

theorem is_greatest_Icc {α : Type u} [preorder α] {a : α} {b : α} (h : a ≤ b) :
    is_greatest (set.Icc a b) b :=
  { left := iff.mpr set.right_mem_Icc h, right := fun (x : α) => and.right }

theorem is_lub_Icc {α : Type u} [preorder α] {a : α} {b : α} (h : a ≤ b) : is_lub (set.Icc a b) b :=
  is_greatest.is_lub (is_greatest_Icc h)

theorem upper_bounds_Icc {α : Type u} [preorder α] {a : α} {b : α} (h : a ≤ b) :
    upper_bounds (set.Icc a b) = set.Ici b :=
  is_lub.upper_bounds_eq (is_lub_Icc h)

theorem is_least_Icc {α : Type u} [preorder α] {a : α} {b : α} (h : a ≤ b) :
    is_least (set.Icc a b) a :=
  { left := iff.mpr set.left_mem_Icc h, right := fun (x : α) => and.left }

theorem is_glb_Icc {α : Type u} [preorder α] {a : α} {b : α} (h : a ≤ b) : is_glb (set.Icc a b) a :=
  is_least.is_glb (is_least_Icc h)

theorem lower_bounds_Icc {α : Type u} [preorder α] {a : α} {b : α} (h : a ≤ b) :
    lower_bounds (set.Icc a b) = set.Iic a :=
  is_glb.lower_bounds_eq (is_glb_Icc h)

theorem is_greatest_Ioc {α : Type u} [preorder α] {a : α} {b : α} (h : a < b) :
    is_greatest (set.Ioc a b) b :=
  { left := iff.mpr set.right_mem_Ioc h, right := fun (x : α) => and.right }

theorem is_lub_Ioc {α : Type u} [preorder α] {a : α} {b : α} (h : a < b) : is_lub (set.Ioc a b) b :=
  is_greatest.is_lub (is_greatest_Ioc h)

theorem upper_bounds_Ioc {α : Type u} [preorder α] {a : α} {b : α} (h : a < b) :
    upper_bounds (set.Ioc a b) = set.Ici b :=
  is_lub.upper_bounds_eq (is_lub_Ioc h)

theorem is_least_Ico {α : Type u} [preorder α] {a : α} {b : α} (h : a < b) :
    is_least (set.Ico a b) a :=
  { left := iff.mpr set.left_mem_Ico h, right := fun (x : α) => and.left }

theorem is_glb_Ico {α : Type u} [preorder α] {a : α} {b : α} (h : a < b) : is_glb (set.Ico a b) a :=
  is_least.is_glb (is_least_Ico h)

theorem lower_bounds_Ico {α : Type u} [preorder α] {a : α} {b : α} (h : a < b) :
    lower_bounds (set.Ico a b) = set.Iic a :=
  is_glb.lower_bounds_eq (is_glb_Ico h)

theorem is_glb_Ioo {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} {b : γ} (hab : a < b) :
    is_glb (set.Ioo a b) a :=
  sorry

theorem lower_bounds_Ioo {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} {b : γ}
    (hab : a < b) : lower_bounds (set.Ioo a b) = set.Iic a :=
  is_glb.lower_bounds_eq (is_glb_Ioo hab)

theorem is_glb_Ioc {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} {b : γ} (hab : a < b) :
    is_glb (set.Ioc a b) a :=
  is_glb.of_subset_of_superset (is_glb_Ioo hab) (is_glb_Icc (le_of_lt hab)) set.Ioo_subset_Ioc_self
    set.Ioc_subset_Icc_self

theorem lower_bound_Ioc {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} {b : γ}
    (hab : a < b) : lower_bounds (set.Ioc a b) = set.Iic a :=
  is_glb.lower_bounds_eq (is_glb_Ioc hab)

theorem is_lub_Ioo {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} {b : γ} (hab : a < b) :
    is_lub (set.Ioo a b) b :=
  sorry

theorem upper_bounds_Ioo {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} {b : γ}
    (hab : a < b) : upper_bounds (set.Ioo a b) = set.Ici b :=
  is_lub.upper_bounds_eq (is_lub_Ioo hab)

theorem is_lub_Ico {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} {b : γ} (hab : a < b) :
    is_lub (set.Ico a b) b :=
  sorry

theorem upper_bounds_Ico {γ : Type w} [linear_order γ] [densely_ordered γ] {a : γ} {b : γ}
    (hab : a < b) : upper_bounds (set.Ico a b) = set.Ici b :=
  is_lub.upper_bounds_eq (is_lub_Ico hab)

theorem bdd_below_iff_subset_Ici {α : Type u} [preorder α] {s : set α} :
    bdd_below s ↔ ∃ (a : α), s ⊆ set.Ici a :=
  iff.rfl

theorem bdd_above_iff_subset_Iic {α : Type u} [preorder α] {s : set α} :
    bdd_above s ↔ ∃ (a : α), s ⊆ set.Iic a :=
  iff.rfl

theorem bdd_below_bdd_above_iff_subset_Icc {α : Type u} [preorder α] {s : set α} :
    bdd_below s ∧ bdd_above s ↔ ∃ (a : α), ∃ (b : α), s ⊆ set.Icc a b :=
  sorry

/-!
### Univ
-/

theorem order_top.upper_bounds_univ {γ : Type w} [order_top γ] :
    upper_bounds set.univ = singleton ⊤ :=
  sorry

theorem is_greatest_univ {γ : Type w} [order_top γ] : is_greatest set.univ ⊤ := sorry

theorem is_lub_univ {γ : Type w} [order_top γ] : is_lub set.univ ⊤ :=
  is_greatest.is_lub is_greatest_univ

theorem order_bot.lower_bounds_univ {γ : Type w} [order_bot γ] :
    lower_bounds set.univ = singleton ⊥ :=
  order_top.upper_bounds_univ

theorem is_least_univ {γ : Type w} [order_bot γ] : is_least set.univ ⊥ := is_greatest_univ

theorem is_glb_univ {γ : Type w} [order_bot γ] : is_glb set.univ ⊥ := is_least.is_glb is_least_univ

theorem no_top_order.upper_bounds_univ {α : Type u} [preorder α] [no_top_order α] :
    upper_bounds set.univ = ∅ :=
  sorry

theorem no_bot_order.lower_bounds_univ {α : Type u} [preorder α] [no_bot_order α] :
    lower_bounds set.univ = ∅ :=
  no_top_order.upper_bounds_univ

/-!
### Empty set
-/

@[simp] theorem upper_bounds_empty {α : Type u} [preorder α] : upper_bounds ∅ = set.univ := sorry

@[simp] theorem lower_bounds_empty {α : Type u} [preorder α] : lower_bounds ∅ = set.univ :=
  upper_bounds_empty

@[simp] theorem bdd_above_empty {α : Type u} [preorder α] [Nonempty α] : bdd_above ∅ := sorry

@[simp] theorem bdd_below_empty {α : Type u} [preorder α] [Nonempty α] : bdd_below ∅ := sorry

theorem is_glb_empty {γ : Type w} [order_top γ] : is_glb ∅ ⊤ := sorry

theorem is_lub_empty {γ : Type w} [order_bot γ] : is_lub ∅ ⊥ := is_glb_empty

theorem is_lub.nonempty {α : Type u} [preorder α] {s : set α} {a : α} [no_bot_order α]
    (hs : is_lub s a) : set.nonempty s :=
  sorry

theorem is_glb.nonempty {α : Type u} [preorder α] {s : set α} {a : α} [no_top_order α]
    (hs : is_glb s a) : set.nonempty s :=
  is_lub.nonempty hs

theorem nonempty_of_not_bdd_above {α : Type u} [preorder α] {s : set α} [ha : Nonempty α]
    (h : ¬bdd_above s) : set.nonempty s :=
  nonempty.elim ha
    fun (x : α) =>
      Exists.imp (fun (a : α) (ha : ∃ (H : a ∈ s), ¬a ≤ x) => Exists.fst ha)
        (iff.mp not_bdd_above_iff' h x)

theorem nonempty_of_not_bdd_below {α : Type u} [preorder α] {s : set α} [ha : Nonempty α]
    (h : ¬bdd_below s) : set.nonempty s :=
  nonempty_of_not_bdd_above h

/-!
### insert
-/

/-- Adding a point to a set preserves its boundedness above. -/
@[simp] theorem bdd_above_insert {γ : Type w} [semilattice_sup γ] (a : γ) {s : set γ} :
    bdd_above (insert a s) ↔ bdd_above s :=
  sorry

theorem bdd_above.insert {γ : Type w} [semilattice_sup γ] (a : γ) {s : set γ} (hs : bdd_above s) :
    bdd_above (insert a s) :=
  iff.mpr (bdd_above_insert a) hs

/--Adding a point to a set preserves its boundedness below.-/
@[simp] theorem bdd_below_insert {γ : Type w} [semilattice_inf γ] (a : γ) {s : set γ} :
    bdd_below (insert a s) ↔ bdd_below s :=
  sorry

theorem bdd_below.insert {γ : Type w} [semilattice_inf γ] (a : γ) {s : set γ} (hs : bdd_below s) :
    bdd_below (insert a s) :=
  iff.mpr (bdd_below_insert a) hs

theorem is_lub.insert {γ : Type w} [semilattice_sup γ] (a : γ) {b : γ} {s : set γ}
    (hs : is_lub s b) : is_lub (insert a s) (a ⊔ b) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_lub (insert a s) (a ⊔ b))) (set.insert_eq a s)))
    (is_lub.union is_lub_singleton hs)

theorem is_glb.insert {γ : Type w} [semilattice_inf γ] (a : γ) {b : γ} {s : set γ}
    (hs : is_glb s b) : is_glb (insert a s) (a ⊓ b) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_glb (insert a s) (a ⊓ b))) (set.insert_eq a s)))
    (is_glb.union is_glb_singleton hs)

theorem is_greatest.insert {γ : Type w} [linear_order γ] (a : γ) {b : γ} {s : set γ}
    (hs : is_greatest s b) : is_greatest (insert a s) (max a b) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_greatest (insert a s) (max a b))) (set.insert_eq a s)))
    (is_greatest.union is_greatest_singleton hs)

theorem is_least.insert {γ : Type w} [linear_order γ] (a : γ) {b : γ} {s : set γ}
    (hs : is_least s b) : is_least (insert a s) (min a b) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_least (insert a s) (min a b))) (set.insert_eq a s)))
    (is_least.union is_least_singleton hs)

@[simp] theorem upper_bounds_insert {α : Type u} [preorder α] (a : α) (s : set α) :
    upper_bounds (insert a s) = set.Ici a ∩ upper_bounds s :=
  sorry

@[simp] theorem lower_bounds_insert {α : Type u} [preorder α] (a : α) (s : set α) :
    lower_bounds (insert a s) = set.Iic a ∩ lower_bounds s :=
  sorry

/-- When there is a global maximum, every set is bounded above. -/
@[simp] protected theorem order_top.bdd_above {γ : Type w} [order_top γ] (s : set γ) :
    bdd_above s :=
  Exists.intro ⊤ fun (a : γ) (ha : a ∈ s) => order_top.le_top a

/-- When there is a global minimum, every set is bounded below. -/
@[simp] protected theorem order_bot.bdd_below {γ : Type w} [order_bot γ] (s : set γ) :
    bdd_below s :=
  Exists.intro ⊥ fun (a : γ) (ha : a ∈ s) => order_bot.bot_le a

/-!
### Pair
-/

theorem is_lub_pair {γ : Type w} [semilattice_sup γ] {a : γ} {b : γ} :
    is_lub (insert a (singleton b)) (a ⊔ b) :=
  is_lub.insert a is_lub_singleton

theorem is_glb_pair {γ : Type w} [semilattice_inf γ] {a : γ} {b : γ} :
    is_glb (insert a (singleton b)) (a ⊓ b) :=
  is_glb.insert a is_glb_singleton

theorem is_least_pair {γ : Type w} [linear_order γ] {a : γ} {b : γ} :
    is_least (insert a (singleton b)) (min a b) :=
  is_least.insert a is_least_singleton

theorem is_greatest_pair {γ : Type w} [linear_order γ] {a : γ} {b : γ} :
    is_greatest (insert a (singleton b)) (max a b) :=
  is_greatest.insert a is_greatest_singleton

/-!
### (In)equalities with the least upper bound and the greatest lower bound
-/

theorem lower_bounds_le_upper_bounds {α : Type u} [preorder α] {s : set α} {a : α} {b : α}
    (ha : a ∈ lower_bounds s) (hb : b ∈ upper_bounds s) : set.nonempty s → a ≤ b :=
  fun (ᾰ : set.nonempty s) =>
    Exists.dcases_on ᾰ fun (ᾰ_w : α) (ᾰ_h : ᾰ_w ∈ s) => idRhs (a ≤ b) (le_trans (ha ᾰ_h) (hb ᾰ_h))

theorem is_glb_le_is_lub {α : Type u} [preorder α] {s : set α} {a : α} {b : α} (ha : is_glb s a)
    (hb : is_lub s b) (hs : set.nonempty s) : a ≤ b :=
  lower_bounds_le_upper_bounds (and.left ha) (and.left hb) hs

theorem is_lub_lt_iff {α : Type u} [preorder α] {s : set α} {a : α} {b : α} (ha : is_lub s a) :
    a < b ↔ ∃ (c : α), ∃ (H : c ∈ upper_bounds s), c < b :=
  sorry

theorem lt_is_glb_iff {α : Type u} [preorder α] {s : set α} {a : α} {b : α} (ha : is_glb s a) :
    b < a ↔ ∃ (c : α), ∃ (H : c ∈ lower_bounds s), b < c :=
  is_lub_lt_iff ha

theorem is_least.unique {α : Type u} [partial_order α] {s : set α} {a : α} {b : α}
    (Ha : is_least s a) (Hb : is_least s b) : a = b :=
  le_antisymm (and.right Ha b (and.left Hb)) (and.right Hb a (and.left Ha))

theorem is_least.is_least_iff_eq {α : Type u} [partial_order α] {s : set α} {a : α} {b : α}
    (Ha : is_least s a) : is_least s b ↔ a = b :=
  { mp := is_least.unique Ha, mpr := fun (h : a = b) => h ▸ Ha }

theorem is_greatest.unique {α : Type u} [partial_order α] {s : set α} {a : α} {b : α}
    (Ha : is_greatest s a) (Hb : is_greatest s b) : a = b :=
  le_antisymm (and.right Hb a (and.left Ha)) (and.right Ha b (and.left Hb))

theorem is_greatest.is_greatest_iff_eq {α : Type u} [partial_order α] {s : set α} {a : α} {b : α}
    (Ha : is_greatest s a) : is_greatest s b ↔ a = b :=
  { mp := is_greatest.unique Ha, mpr := fun (h : a = b) => h ▸ Ha }

theorem is_lub.unique {α : Type u} [partial_order α] {s : set α} {a : α} {b : α} (Ha : is_lub s a)
    (Hb : is_lub s b) : a = b :=
  is_least.unique Ha Hb

theorem is_glb.unique {α : Type u} [partial_order α] {s : set α} {a : α} {b : α} (Ha : is_glb s a)
    (Hb : is_glb s b) : a = b :=
  is_greatest.unique Ha Hb

theorem lt_is_lub_iff {α : Type u} [linear_order α] {s : set α} {a : α} {b : α} (h : is_lub s a) :
    b < a ↔ ∃ (c : α), ∃ (H : c ∈ s), b < c :=
  sorry

theorem is_glb_lt_iff {α : Type u} [linear_order α] {s : set α} {a : α} {b : α} (h : is_glb s a) :
    a < b ↔ ∃ (c : α), ∃ (H : c ∈ s), c < b :=
  lt_is_lub_iff h

theorem is_lub.exists_between {α : Type u} [linear_order α] {s : set α} {a : α} {b : α}
    (h : is_lub s a) (hb : b < a) : ∃ (c : α), ∃ (H : c ∈ s), b < c ∧ c ≤ a :=
  sorry

theorem is_lub.exists_between' {α : Type u} [linear_order α] {s : set α} {a : α} {b : α}
    (h : is_lub s a) (h' : ¬a ∈ s) (hb : b < a) : ∃ (c : α), ∃ (H : c ∈ s), b < c ∧ c < a :=
  sorry

theorem is_glb.exists_between {α : Type u} [linear_order α] {s : set α} {a : α} {b : α}
    (h : is_glb s a) (hb : a < b) : ∃ (c : α), ∃ (H : c ∈ s), a ≤ c ∧ c < b :=
  sorry

theorem is_glb.exists_between' {α : Type u} [linear_order α] {s : set α} {a : α} {b : α}
    (h : is_glb s a) (h' : ¬a ∈ s) (hb : a < b) : ∃ (c : α), ∃ (H : c ∈ s), a < c ∧ c < b :=
  sorry

/-!
### Least upper bound and the greatest lower bound in linear ordered additive commutative groups
-/

theorem is_glb.exists_between_self_add {α : Type u} [linear_ordered_add_comm_group α] {s : set α}
    {a : α} {ε : α} (h : is_glb s a) (hε : 0 < ε) : ∃ (b : α), ∃ (H : b ∈ s), a ≤ b ∧ b < a + ε :=
  is_glb.exists_between h (lt_add_of_pos_right a hε)

theorem is_glb.exists_between_self_add' {α : Type u} [linear_ordered_add_comm_group α] {s : set α}
    {a : α} {ε : α} (h : is_glb s a) (h₂ : ¬a ∈ s) (hε : 0 < ε) :
    ∃ (b : α), ∃ (H : b ∈ s), a < b ∧ b < a + ε :=
  is_glb.exists_between' h h₂ (lt_add_of_pos_right a hε)

theorem is_lub.exists_between_sub_self {α : Type u} [linear_ordered_add_comm_group α] {s : set α}
    {a : α} {ε : α} (h : is_lub s a) (hε : 0 < ε) : ∃ (b : α), ∃ (H : b ∈ s), a - ε < b ∧ b ≤ a :=
  is_lub.exists_between h (sub_lt_self a hε)

theorem is_lub.exists_between_sub_self' {α : Type u} [linear_ordered_add_comm_group α] {s : set α}
    {a : α} {ε : α} (h : is_lub s a) (h₂ : ¬a ∈ s) (hε : 0 < ε) :
    ∃ (b : α), ∃ (H : b ∈ s), a - ε < b ∧ b < a :=
  is_lub.exists_between' h h₂ (sub_lt_self a hε)

/-!
### Images of upper/lower bounds under monotone functions
-/

namespace monotone

theorem mem_upper_bounds_image {α : Type u} {β : Type v} [preorder α] [preorder β] {f : α → β}
    (Hf : monotone f) {a : α} {s : set α} (Ha : a ∈ upper_bounds s) : f a ∈ upper_bounds (f '' s) :=
  set.ball_image_of_ball fun (x : α) (H : x ∈ s) => Hf (Ha H)

theorem mem_lower_bounds_image {α : Type u} {β : Type v} [preorder α] [preorder β] {f : α → β}
    (Hf : monotone f) {a : α} {s : set α} (Ha : a ∈ lower_bounds s) : f a ∈ lower_bounds (f '' s) :=
  set.ball_image_of_ball fun (x : α) (H : x ∈ s) => Hf (Ha H)

/-- The image under a monotone function of a set which is bounded above is bounded above. -/
theorem map_bdd_above {α : Type u} {β : Type v} [preorder α] [preorder β] {f : α → β} {s : set α}
    (hf : monotone f) : bdd_above s → bdd_above (f '' s) :=
  sorry

/-- The image under a monotone function of a set which is bounded below is bounded below. -/
theorem map_bdd_below {α : Type u} {β : Type v} [preorder α] [preorder β] {f : α → β} {s : set α}
    (hf : monotone f) : bdd_below s → bdd_below (f '' s) :=
  sorry

/-- A monotone map sends a least element of a set to a least element of its image. -/
theorem map_is_least {α : Type u} {β : Type v} [preorder α] [preorder β] {f : α → β}
    (Hf : monotone f) {a : α} {s : set α} (Ha : is_least s a) : is_least (f '' s) (f a) :=
  { left := set.mem_image_of_mem f (and.left Ha),
    right := mem_lower_bounds_image Hf (and.right Ha) }

/-- A monotone map sends a greatest element of a set to a greatest element of its image. -/
theorem map_is_greatest {α : Type u} {β : Type v} [preorder α] [preorder β] {f : α → β}
    (Hf : monotone f) {a : α} {s : set α} (Ha : is_greatest s a) : is_greatest (f '' s) (f a) :=
  { left := set.mem_image_of_mem f (and.left Ha),
    right := mem_upper_bounds_image Hf (and.right Ha) }

theorem is_lub_image_le {α : Type u} {β : Type v} [preorder α] [preorder β] {f : α → β}
    (Hf : monotone f) {a : α} {s : set α} (Ha : is_lub s a) {b : β} (Hb : is_lub (f '' s) b) :
    b ≤ f a :=
  and.right Hb (f a) (mem_upper_bounds_image Hf (and.left Ha))

theorem le_is_glb_image {α : Type u} {β : Type v} [preorder α] [preorder β] {f : α → β}
    (Hf : monotone f) {a : α} {s : set α} (Ha : is_glb s a) {b : β} (Hb : is_glb (f '' s) b) :
    f a ≤ b :=
  and.right Hb (f a) (mem_lower_bounds_image Hf (and.left Ha))

end monotone

theorem is_glb.of_image {α : Type u} {β : Type v} [preorder α] [preorder β] {f : α → β}
    (hf : ∀ {x y : α}, f x ≤ f y ↔ x ≤ y) {s : set α} {x : α} (hx : is_glb (f '' s) (f x)) :
    is_glb s x :=
  sorry

theorem is_lub.of_image {α : Type u} {β : Type v} [preorder α] [preorder β] {f : α → β}
    (hf : ∀ {x y : α}, f x ≤ f y ↔ x ≤ y) {s : set α} {x : α} (hx : is_lub (f '' s) (f x)) :
    is_lub s x :=
  is_glb.of_image (fun (x y : order_dual α) => hf) hx

end Mathlib


variable (A B C : Prop)

example : A ∧ B := sorry

example : ¬ A := sorry

example : A ∨ B := sorry

example : A → B := sorry

example : A ↔ B := sorry

-- Elimination and introduction for →

example (a : A) : A := a

example : True := trivial

example : A → A := fun (a:A) => a

theorem myAwesomeTheorem ( a : A ) : B :=
sorry

#check myAwesomeTheorem A B

-- introduction
example : A → B := myAwesomeTheorem A B

-- elimination
example (a : A) (f : A → B) : B := f a

example : A → B → A := fun (a : A) =>
fun (_ : B) => a

-- Elimination and introduction for ∧

example (p : A ∧ B) : A := And.left p

example (p : A ∧ B) : B := And.right p

#check And.left

example (a : A) (b : B) : A ∧ B := And.intro a b

#check And.intro

-- Elimination and introduction for ∨

example (a : A) : A ∨ B := Or.inl a

example (b : B) : A ∨ B := Or.inr b

#check Or.elim

example (h : A ∨ B) : B ∨ A :=
  Or.elim h (Or.intro_right B) (Or.intro_left A)

-- Elimination and introduction for \iff

example (f : A → B) (g : B → A) : A ↔ B :=
  Iff.intro f g

example (h : A ↔ B) (a : A) : B := Iff.mp h a

example (h : A ↔ B) (b : B) : A := Iff.mpr h b

#check @Iff.mpr A B


import Chap4
namespace HTPI
set_option pp.funBinderTypes true

/- Definitions and theorems in HTPIDefs
def graph {A B : Type} (f : A → B) : Set (A × B) :=
  { (a, b) : A × B | f a = b }

def is_func_graph {A B : Type} (G : Set (A × B)) : Prop :=
  ∀ (x : A), ∃! (y : B), (x, y) ∈ G

theorem func_from_graph {A B : Type} (F : Set (A × B)) :
    (∃ (f : A → B), graph f = F) ↔ is_func_graph F := by
-/

/- Definitions -/
def onto {A B : Type} (f : A → B) : Prop :=
  ∀ (y : B), ∃ (x : A), f x = y

def one_to_one {A B : Type} (f : A → B) : Prop :=
  ∀ (x1 x2 : A), f x1 = f x2 → x1 = x2

def closed {A : Type} (f : A → A) (C : Set A) : Prop := ∀ x ∈ C, f x ∈ C

def closure {A : Type} (f : A → A) (B C : Set A) : Prop :=
  smallestElt (sub A) C { D : Set A | B ⊆ D ∧ closed f D }

def closed2 {A : Type} (f : A → A → A) (C : Set A) : Prop :=
  ∀ x ∈ C, ∀ y ∈ C, f x y ∈ C

def closure2 {A : Type} (f : A → A → A) (B C : Set A) : Prop :=
  smallestElt (sub A) C { D : Set A | B ⊆ D ∧ closed2 f D }

def closed_family {A : Type} (F : Set (A → A)) (C : Set A) : Prop :=
  ∀ f ∈ F, closed f C

def closure_family {A : Type} (F : Set (A → A)) (B C : Set A) : Prop :=
  smallestElt (sub A) C { D : Set A | B ⊆ D ∧ closed_family F D }

def image {A B : Type} (f : A → B) (X : Set A) : Set B :=
  { f x | x ∈ X }

def inverse_image {A B : Type} (f : A → B) (Y : Set B) : Set A :=
  { a : A | f a ∈ Y }

/- Section 5.1 -/
theorem graph_def {A B : Type} (f : A → B) (a : A) (b : B) :
    (a, b) ∈ graph f ↔ f a = b := by rfl

theorem Theorem_5_1_4 {A B : Type} (f g : A → B) :
    (∀ (a : A), f a = g a) → f = g := funext

example {A B : Type} (f g : A → B) :
    graph f = graph g → f = g := by
  assume h1 : graph f = graph g  --Goal : f = g
  apply funext                   --Goal : ∀ (x : A), f x = g x
  fix x : A
  have h2 : (x, f x) ∈ graph f := by
    define                       --Goal : f x = f x
    rfl
    done
  rewrite [h1] at h2             --h2 : (x, f x) ∈ graph g
  define at h2                   --h2 : g x = f x
  show f x = g x from h2.symm
  done

def square1 (n : Nat) : Nat := n ^ 2

def square2 : Nat → Nat := fun (n : Nat) => n ^ 2

example : square1 = square2 := by rfl

#eval square1 7     --Answer: 49

theorem Theorem_5_1_5 {A B C : Type} (f : A → B) (g : B → C) :
    ∃ (h : A → C), graph h = comp (graph g) (graph f) := by
  let h : A → C := fun (x : A) => g (f x)
  apply Exists.intro h
  apply Set.ext
  fix (a, c) : A × C
  apply Iff.intro
  · -- Proof that (a, c) ∈ graph h → (a, c) ∈ comp (graph g) (graph f)
    assume h1 : (a, c) ∈ graph h
    define at h1  --h1 : h a = c
    define        --Goal : ∃ (x : B), (a, x) ∈ graph f ∧ (x, c) ∈ graph g
    apply Exists.intro (f a)
    apply And.intro
    · -- Proof that (a, f a) ∈ graph f
      define
      rfl
      done
    · -- Proof that (f a, c) ∈ graph g
      define
      show g (f a) = c from h1
      done
    done
  · -- Proof that (a, c) ∈ comp (graph g) (graph f) → (a, c) ∈ graph h
    assume h1 : (a, c) ∈ comp (graph g) (graph f)
    define        --Goal : h a = c
    define at h1  --h1 : ∃ (x : B), (a, x) ∈ graph f ∧ (x, c) ∈ graph g
    obtain (b : B) (h2 : (a, b) ∈ graph f ∧ (b, c) ∈ graph g) from h1
    have h3 : (a, b) ∈ graph f := h2.left
    have h4 : (b, c) ∈ graph g := h2.right
    define at h3          --h3 : f a = b
    define at h4          --h4 : g b = c
    rewrite [←h3] at h4   --h4 : g (f a) = c
    show h a = c from h4
    done
  done

example {A B C D : Type} (f : A → B) (g : B → C) (h : C → D) :
    h ∘ (g ∘ f) = (h ∘ g) ∘ f := by rfl

example {A B : Type} (f : A → B) : f ∘ id = f := by rfl

example {A B : Type} (f : A → B) : id ∘ f = f := by rfl

/- Section 5.2 -/
theorem Theorem_5_2_5_1 {A B C : Type} (f : A → B) (g : B → C) :
    one_to_one f → one_to_one g → one_to_one (g ∘ f) := by
  assume h1 : one_to_one f
  assume h2 : one_to_one g
  define at h1  --h1 : ∀ (x1 x2 : A), f x1 = f x2 → x1 = x2
  define at h2  --h2 : ∀ (x1 x2 : B), g x1 = g x2 → x1 = x2
  define        --Goal : ∀ (x1 x2 : A), (g ∘ f) x1 = (g ∘ f) x2 → x1 = x2
  fix a1 : A
  fix a2 : A    --Goal : (g ∘ f) a1 = (g ∘ f) a2 → a1 = a2
  define : (g ∘ f) a1; define : (g ∘ f) a2
                --Goal : g (f a1) = g (f a2) → a1 = a2
  assume h3 : g (f a1) = g (f a2)
  have h4 : f a1 = f a2 := h2 (f a1) (f a2) h3
  show a1 = a2 from h1 a1 a2 h4
  done

theorem Theorem_5_2_5_2 {A B C : Type} (f : A → B) (g : B → C) :
    onto f → onto g → onto (g ∘ f) := by
  assume h1 : onto f
  assume h2 : onto g
  define at h1           --h1 : ∀ (y : B), ∃ (x : A), f x = y
  define at h2           --h2 : ∀ (y : C), ∃ (x : B), g x = y
  define                 --Goal : ∀ (y : C), ∃ (x : A), (g ∘ f) x = y
  fix c : C
  obtain (b : B) (h3 : g b = c) from h2 c
  obtain (a : A) (h4 : f a = b) from h1 b
  apply Exists.intro a   --Goal : (g ∘ f) a = c
  define : (g ∘ f) a     --Goal : g (f a) = c
  rewrite [←h4] at h3
  show g (f a) = c from h3
  done

/- Section 5.3 -/
theorem Theorem_5_3_1 {A B : Type}
    (f : A → B) (h1 : one_to_one f) (h2 : onto f) :
    ∃ (g : B → A), graph g = inv (graph f) := by
  rewrite [func_from_graph]   --Goal : is_func_graph (inv (graph f))
  define        --Goal : ∀ (x : B), ∃! (y : A), (x, y) ∈ inv (graph f)
  fix b : B
  exists_unique
  · -- Existence
    define at h2          --h2 : ∀ (y : B), ∃ (x : A), f x = y
    obtain (a : A) (h4 : f a = b) from h2 b
    apply Exists.intro a  --Goal : (b, a) ∈ inv (graph f)
    define                --Goal : f a = b
    show f a = b from h4
    done
  · -- Uniqueness
    fix a1 : A; fix a2 : A
    assume h3 : (b, a1) ∈ inv (graph f)
    assume h4 : (b, a2) ∈ inv (graph f) --Goal : a1 = a2
    define at h3          --h3 : f a1 = b
    define at h4          --h4 : f a2 = b
    rewrite [←h4] at h3   --h3 : f a1 = f a2
    define at h1          --h1 : ∀ (x1 x2 : A), f x1 = f x2 → x1 = x2
    show a1 = a2 from h1 a1 a2 h3
    done
  done

theorem Theorem_5_3_2_1 {A B : Type} (f : A → B) (g : B → A)
    (h1 : graph g = inv (graph f)) : g ∘ f = id := by
  apply funext           --Goal : ∀ (x : A), (g ∘ f) x = id x
  fix a : A              --Goal : (g ∘ f) a = id a
  have h2 : (f a, a) ∈ graph g := by
    rewrite [h1]         --Goal : (f a, a) ∈ inv (graph f)
    define               --Goal : f a = f a
    rfl
    done
  define at h2           --h2 : g (f a) = a
  show (g ∘ f) a = id a from h2
  done

theorem Theorem_5_3_2_2 {A B : Type} (f : A → B) (g : B → A)
    (h1 : graph g = inv (graph f)) : f ∘ g = id := sorry

theorem Theorem_5_3_3_1 {A B : Type} (f : A → B) :
    (∃ (g : B → A), g ∘ f = id) → one_to_one f := by
  assume h1 : ∃ (g : B → A), g ∘ f = id
  obtain (g : B → A) (h2 : g ∘ f = id) from h1
  define              --Goal : ∀ (x1 x2 : A), f x1 = f x2 → x1 = x2
  fix a1 : A; fix a2 : A
  assume h3 : f a1 = f a2
  show a1 = a2 from
    calc a1
      _ = id a1 := by rfl
      _ = (g ∘ f) a1 := by rw [h2]
      _ = g (f a1) := by rfl
      _ = g (f a2) := by rw [h3]
      _ = (g ∘ f) a2 := by rfl
      _ = id a2 := by rw [h2]
      _ = a2 := by rfl
  done

theorem Theorem_5_3_3_2 {A B : Type} (f : A → B) :
    (∃ (g : B → A), f ∘ g = id) → onto f := sorry

theorem Theorem_5_3_5 {A B : Type} (f : A → B) (g : B → A)
    (h1 : g ∘ f = id) (h2 : f ∘ g = id) : graph g = inv (graph f) := by
  have h3 : one_to_one f := Theorem_5_3_3_1 f (Exists.intro g h1)
  have h4 : onto f := Theorem_5_3_3_2 f (Exists.intro g h2)
  obtain (g' : B → A) (h5 : graph g' = inv (graph f))
    from Theorem_5_3_1 f h3 h4
  have h6 : g' ∘ f = id := Theorem_5_3_2_1 f g' h5
  have h7 : g = g' :=
    calc g
      _ = id ∘ g := by rfl
      _ = (g' ∘ f) ∘ g := by rw [h6]
      _ = g' ∘ (f ∘ g) := by rfl
      _ = g' ∘ id := by rw [h2]
      _ = g' := by rfl
  rewrite [←h7] at h5
  show graph g = inv (graph f) from h5
  done

/- Section 5.4 -/
theorem Theorem_5_4_5 {A : Type} (f : A → A) (B : Set A) :
    ∃ (C : Set A), closure f B C := by
  let F : Set (Set A) := { D : Set A | B ⊆ D ∧ closed f D }
  let C : Set A := ⋂₀ F
  apply Exists.intro C    --Goal : closure f B C
  define                  --Goal : C ∈ F ∧ ∀ x ∈ F, C ⊆ x
  apply And.intro
  · -- Proof that C ∈ F
    define                  --Goal : B ⊆ C ∧ closed f C
    apply And.intro
    · -- Proof that B ⊆ C
      fix a : A
      assume h1 : a ∈ B       --Goal : a ∈ C
      define                  --Goal : ∀ t ∈ F, a ∈ t
      fix D : Set A
      assume h2 : D ∈ F
      define at h2            --h2 : B ⊆ D ∧ closed f D
      show a ∈ D from h2.left h1
      done
    · -- Proof that C is closed under f
      define                  --Goal : ∀ x ∈ C, f x ∈ C
      fix a : A
      assume h1 : a ∈ C       --Goal : f a ∈ C
      define                  --Goal : ∀ t ∈ F, f a ∈ t
      fix D : Set A
      assume h2 : D ∈ F       --Goal : f a ∈ D
      define at h1            --h1 : ∀ t ∈ F, a ∈ t
      have h3 : a ∈ D := h1 D h2
      define at h2            --h2 : B ⊆ D ∧ closed f D
      have h4 : closed f D := h2.right
      define at h4            --h4 : ∀ x ∈ D, f x ∈ D
      show f a ∈ D from h4 a h3
      done
    done
  · -- Proof that C is smallest
    fix D : Set A
    assume h1 : D ∈ F      --Goal : sub A C D
    define
    fix a : A
    assume h2 : a ∈ C       --Goal : a ∈ D
    define at h2            --h2 : ∀ t ∈ F, a ∈ t
    show a ∈ D from h2 D h1
    done
  done

def plus (m n : Int) : Int := m + n

def plus' : Int → Int → Int := fun (m n : Int) => m + n

def plus'' : Int → Int → Int := fun (m : Int) => (fun (n : Int) => m + n)

example : plus = plus'' := by rfl

example : plus' = plus'' := by rfl

#eval plus 3 2     --Answer: 5

theorem Theorem_5_4_9 {A : Type} (f : A → A → A) (B : Set A) :
    ∃ (C : Set A), closure2 f B C := sorry

/- Section 5.5 -/
theorem image_def {A B : Type} (f : A → B) (X : Set A) (b : B) :
    b ∈ image f X ↔ ∃ x ∈ X, f x = b := by rfl

theorem inverse_image_def {A B : Type} (f : A → B) (Y : Set B) (a : A) :
    a ∈ inverse_image f Y ↔ f a ∈ Y := by rfl

theorem Theorem_5_5_2_1 {A B : Type} (f : A → B) (W X : Set A) :
    image f (W ∩ X) ⊆ image f W ∩ image f X := by
  fix y : B
  assume h1 : y ∈ image f (W ∩ X)  --Goal : y ∈ image f W ∩ image f X
  define at h1                     --h1 : ∃ (x : A), x ∈ W ∩ X ∧ f x = y
  obtain (x : A) (h2 : x ∈ W ∩ X ∧ f x = y) from h1
  define : x ∈ W ∩ X at h2         --h2 : (x ∈ W ∧ x ∈ X) ∧ f x = y
  apply And.intro
  · -- Proof that y ∈ image f W
    define                         --Goal : ∃ (x : A), x ∈ W ∧ f x = y
    show ∃ (x : A), x ∈ W ∧ f x = y from
      Exists.intro x (And.intro h2.left.left h2.right)
    done
  · -- Proof that y ∈ image f X
    show y ∈ image f X from
      Exists.intro x (And.intro h2.left.right h2.right)
    done
  done

theorem Theorem_5_5_2_2 {A B : Type} (f : A → B) (W X : Set A)
    (h1 : one_to_one f) : image f (W ∩ X) = image f W ∩ image f X := by
  apply Set.ext
  fix y : B      --Goal : y ∈ image f (W ∩ X) ↔ y ∈ image f W ∩ image f X
  apply Iff.intro
  · -- (→)
    assume h2 : y ∈ image f (W ∩ X)
    show y ∈ image f W ∩ image f X from Theorem_5_5_2_1 f W X h2
    done
  · -- (←)
    assume h2 : y ∈ image f W ∩ image f X  --Goal : y ∈ image f (W ∩ X)
    define at h2                  --h2 : y ∈ image f W ∧ y ∈ image f X
    rewrite [image_def, image_def] at h2
          --h2 : (∃ (x : A), x ∈ W ∧ f x = y) ∧ ∃ (x : A), x ∈ X ∧ f x = y
    obtain (x1 : A) (h3 : x1 ∈ W ∧ f x1 = y) from h2.left
    obtain (x2 : A) (h4 : x2 ∈ X ∧ f x2 = y) from h2.right
    have h5 : f x2 = y := h4.right
    rewrite [←h3.right] at h5  --h5 : f x2 = f x1
    define at h1               --h1 : ∀ (x1 x2 : A), f x1 = f x2 → x1 = x2
    have h6 : x2 = x1 := h1 x2 x1 h5
    rewrite [h6] at h4           --h4 : x1 ∈ X ∧ f x1 = y
    show y ∈ image f (W ∩ X) from
      Exists.intro x1 (And.intro (And.intro h3.left h4.left) h3.right)
    done
  done


-- El_cociente_aplica_relaciones_de_equivalencia_en_particiones.lean
-- El cociente aplica relaciones de equivalencia en particiones
-- José A. Alonso Jiménez
-- Sevilla, 8 de octubre de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Definir la función
--    cociente : {R : A → A → Prop // equivalence R} → particion A
-- tal que (cociente R) es la partición de A formada por las clases de
-- equivalencia de la relación de equivalencia R.
-- ---------------------------------------------------------------------

import tactic

@[ext] structure particion (A : Type) :=
(Bloques    : set (set A))
(Hno_vacios : ∀ X ∈ Bloques, (X : set A).nonempty)
(Hrecubren  : ∀ a, ∃ X ∈ Bloques, a ∈ X)
(Hdisjuntos : ∀ X Y ∈ Bloques, (X ∩ Y : set A).nonempty → X = Y)

namespace particion

variable  {A : Type}
variable  {P : particion A}
variables {X Y : set A}
variable  (R : A → A → Prop)

def clase (a : A) :=
  {b : A | R b a}

def clases : (A → A → Prop) → set (set A) :=
  λ R, {B : set A | ∃ x : A, B = clase R x}

lemma pertenece_clase_syss
  {a b : A}
  : b ∈ clase R a ↔ R b a :=
by refl

lemma clases_no_vacias
  (hR: equivalence R)
  : ∀ (X : set A), X ∈ clases R → X.nonempty :=
begin
  rintros _ ⟨a, rfl⟩,
  use a,
  rw pertenece_clase_syss,
  apply hR.1,
end

lemma clases_recubren
  (hR: equivalence R)
  : ∀ a, ∃ X ∈ clases R, a ∈ X :=
begin
  intro a,
  use clase R a,
  split,
  { use a, },
  { exact hR.1 a, },
end

lemma subclase_si_pertenece
  {R : A → A → Prop}
  (hR: equivalence R)
  {a b : A}
  : a ∈ clase R b → clase R a ⊆ clase R b :=
λ hab z hza, hR.2.2 hza hab

lemma clases_iguales_si_pertenece
  {R : A → A → Prop}
  (hR: equivalence R)
  {a b : A}
  : a ∈ clase R b → clase R a = clase R b :=
λ hab, set.subset.antisymm
        (subclase_si_pertenece hR hab)
        (subclase_si_pertenece hR (hR.2.1 hab))

lemma clases_disjuntas
  (hR: equivalence R)
  : ∀ X Y ∈ clases R, (X ∩ Y : set A).nonempty → X = Y :=
begin
  rintros X ⟨a, rfl⟩ Y ⟨b, rfl⟩ ⟨c, hca, hcb⟩,
  exact clases_iguales_si_pertenece hR (hR.2.2 (hR.2.1 hca) hcb),
end

def cociente : {R : A → A → Prop // equivalence R} → particion A :=
  λ R, { Bloques    := {B : set A | ∃ x : A, B = clase R.1 x},
         Hno_vacios := clases_no_vacias R.1 R.2,
         Hrecubren  := clases_recubren R.1 R.2,
         Hdisjuntos := clases_disjuntas R.1 R.2, }

end particion


/-
Copyright (c) 2021 Alex J. Best. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alex J. Best
-/
import analysis.convex.measure
import measure_theory.group.fundamental_domain
import measure_theory.measure.haar_lebesgue

/-!
# Geometry of numbers

In this file we prove some of the fundamental theorems in the geometry of numbers, as studied by
Hermann Minkowski.

## Main results

* `exists_pair_mem_lattice_not_disjoint_vadd`: Blichfeldt's principle, existence of two distinct
  points in a subgroup such that the translates of a set by these two points are not disjoint when
  the covolume of the subgroup is larger than the volume of the
* `exists_ne_zero_mem_lattice_of_measure_mul_two_pow_lt_measure`: Minkowski's theorem, existence of
  a non-zero lattice point inside a convex symmetric domain of large enough volume.

## TODO

* Calculate the volume of the fundamental domain of a finite index subgroup
* Voronoi diagrams
* See [Pete L. Clark, *Abstract Geometry of Numbers: Linear Forms* (arXiv)](https://arxiv.org/abs/1405.2119)
  for some more ideas.

## References

* [Pete L. Clark, *Geometry of Numbers with Applications to Number Theory*][clark_gon] p.28
-/

namespace measure_theory

open ennreal finite_dimensional measure_theory measure_theory.measure set
open_locale pointwise

variables {E L : Type*} [measurable_space E] {μ : measure E} {F s : set E}

/-- **Blichfeldt's Theorem**. If the volume of the set `s` is larger than the covolume of the
countable subgroup `L` of `E`, then there exists two distincts points `x, y ∈ L` such that `(x + s)`
and `(y + s)` are not disjoint. -/
lemma exists_pair_mem_lattice_not_disjoint_vadd [add_comm_group L] [countable L]
  [add_action L E] [measurable_space L] [has_measurable_vadd L E] [vadd_invariant_measure L E μ]
  (fund : is_add_fundamental_domain L F μ) (hS : null_measurable_set s μ) (h : μ F < μ s) :
  ∃ x y : L, x ≠ y ∧ ¬ disjoint (x +ᵥ s) (y +ᵥ s) :=
begin
  contrapose! h,
  exact ((fund.measure_eq_tsum _).trans (measure_Union₀ (pairwise.mono h $ λ i j hij,
    (hij.mono inf_le_left inf_le_left).ae_disjoint) $ λ _,
    (hS.vadd _).inter fund.null_measurable_set).symm).trans_le
    (measure_mono $ Union_subset $ λ _, inter_subset_right _ _),
end

/-- The **Minkowksi Convex Body Theorem**. If `s` is a convex symmetric domain of `E` whose volume
is large enough compared to the covolume of a lattice `L` of `E`, then it contains a non-zero
lattice point of `L`.  -/
lemma exists_ne_zero_mem_lattice_of_measure_mul_two_pow_lt_measure [normed_add_comm_group E]
  [normed_space ℝ E] [borel_space E] [finite_dimensional ℝ E] [is_add_haar_measure μ]
  {L : add_subgroup E} [countable L] (fund : is_add_fundamental_domain L F μ)
  (h : μ F * 2 ^ finrank ℝ E < μ s) (h_symm : ∀ x ∈ s, -x ∈ s) (h_conv : convex ℝ s) :
  ∃ x ≠ 0, ((x : L) : E) ∈ s :=
begin
  have h_vol : μ F < μ ((2⁻¹ : ℝ) • s),
  { rwa [add_haar_smul_of_nonneg μ (by norm_num : 0 ≤ (2 : ℝ)⁻¹) s, ←mul_lt_mul_right
      (pow_ne_zero (finrank ℝ E) (two_ne_zero' _)) (pow_ne_top two_ne_top), mul_right_comm,
      of_real_pow (by norm_num : 0 ≤ (2 : ℝ)⁻¹), ←of_real_inv_of_pos zero_lt_two, of_real_bit0,
      of_real_one, ←mul_pow, ennreal.inv_mul_cancel two_ne_zero two_ne_top, one_pow, one_mul] },
  obtain ⟨x, y, hxy, h⟩ := exists_pair_mem_lattice_not_disjoint_vadd fund
    ((h_conv.smul _).null_measurable_set _) h_vol,
  obtain ⟨_, ⟨v, hv, rfl⟩, w, hw, hvw⟩ := not_disjoint_iff.mp h,
  refine ⟨x - y, sub_ne_zero.2 hxy, _⟩,
  rw mem_inv_smul_set_iff₀ (two_ne_zero' ℝ) at hv hw,
  simp_rw [add_subgroup.vadd_def, vadd_eq_add, add_comm _ w, ←sub_eq_sub_iff_add_eq_add,
    ←add_subgroup.coe_sub] at hvw,
  rw [←hvw, ←inv_smul_smul₀ (two_ne_zero' ℝ) (_ - _), smul_sub, sub_eq_add_neg, smul_add],
  refine h_conv hw (h_symm _ hv) _ _ _; norm_num,
end

end measure_theory


/-
Copyright (c) 2022 Joseph Myers. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joseph Myers
-/
import analysis.inner_product_space.projection
import linear_algebra.orientation

/-!
# Orientations of real inner product spaces.

This file provides definitions and proves lemmas about orientations of real inner product spaces.

## Main definitions

* `orientation.fin_orthonormal_basis` is an orthonormal basis, indexed by `fin n`, with the given
orientation.

-/

noncomputable theory

variables {E : Type*} [inner_product_space ℝ E]
variables {ι : Type*} [fintype ι] [decidable_eq ι]

open finite_dimensional

/-- `basis.adjust_to_orientation`, applied to an orthonormal basis, produces an orthonormal
basis. -/
lemma orthonormal.orthonormal_adjust_to_orientation [nonempty ι] {e : basis ι ℝ E}
  (h : orthonormal ℝ e) (x : orientation ℝ E ι) : orthonormal ℝ (e.adjust_to_orientation x) :=
h.orthonormal_of_forall_eq_or_eq_neg (e.adjust_to_orientation_apply_eq_or_eq_neg x)

/-- An orthonormal basis, indexed by `fin n`, with the given orientation. -/
protected def orientation.fin_orthonormal_basis {n : ℕ} (hn : 0 < n) (h : finrank ℝ E = n)
  (x : orientation ℝ E (fin n)) : basis (fin n) ℝ E :=
begin
  haveI := fin.pos_iff_nonempty.1 hn,
  haveI := finite_dimensional_of_finrank (h.symm ▸ hn : 0 < finrank ℝ E),
  exact (fin_std_orthonormal_basis h).adjust_to_orientation x
end

/-- `orientation.fin_orthonormal_basis` is orthonormal. -/
protected lemma orientation.fin_orthonormal_basis_orthonormal {n : ℕ} (hn : 0 < n)
  (h : finrank ℝ E = n) (x : orientation ℝ E (fin n)) :
  orthonormal ℝ (x.fin_orthonormal_basis hn h) :=
begin
  haveI := fin.pos_iff_nonempty.1 hn,
  haveI := finite_dimensional_of_finrank (h.symm ▸ hn : 0 < finrank ℝ E),
  exact (fin_std_orthonormal_basis_orthonormal h).orthonormal_adjust_to_orientation _
end

/-- `orientation.fin_orthonormal_basis` gives a basis with the required orientation. -/
@[simp] lemma orientation.fin_orthonormal_basis_orientation {n : ℕ} (hn : 0 < n)
  (h : finrank ℝ E = n) (x : orientation ℝ E (fin n)) :
  (x.fin_orthonormal_basis hn h).orientation = x :=
begin
  haveI := fin.pos_iff_nonempty.1 hn,
  exact basis.orientation_adjust_to_orientation _ _
end