/-
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 order.monotone.odd
import analysis.special_functions.exp_deriv
import analysis.special_functions.trigonometric.basic
import data.set.intervals.monotone

/-!
# 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 topology 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_add_comm_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 }

/-- `sinh` is injective, `∀ a b, sinh a = sinh b → a = b`. -/
lemma sinh_injective : function.injective sinh := sinh_strict_mono.injective

@[simp] lemma sinh_inj : sinh x = sinh y ↔ x = y := sinh_injective.eq_iff
@[simp] lemma sinh_le_sinh : sinh x ≤ sinh y ↔ x ≤ y := sinh_strict_mono.le_iff_le
@[simp] lemma sinh_lt_sinh : sinh x < sinh y ↔ x < y := sinh_strict_mono.lt_iff_lt

@[simp] lemma sinh_pos_iff : 0 < sinh x ↔ 0 < x :=
by simpa only [sinh_zero] using @sinh_lt_sinh 0 x

@[simp] lemma sinh_nonpos_iff : sinh x ≤ 0 ↔ x ≤ 0 :=
by simpa only [sinh_zero] using @sinh_le_sinh x 0

@[simp] lemma sinh_neg_iff : sinh x < 0 ↔ x < 0 :=
by simpa only [sinh_zero] using @sinh_lt_sinh x 0

@[simp] lemma sinh_nonneg_iff : 0 ≤ sinh x ↔ 0 ≤ x :=
by simpa only [sinh_zero] using @sinh_le_sinh 0 x

lemma abs_sinh (x : ℝ) : |sinh x| = sinh (|x|) :=
by cases le_total x 0; simp [abs_of_nonneg, abs_of_nonpos, *]

lemma cosh_strict_mono_on : strict_mono_on cosh (Ici 0) :=
(convex_Ici _).strict_mono_on_of_deriv_pos continuous_cosh.continuous_on $ λ x hx,
  by { rw [interior_Ici, mem_Ioi] at hx, rwa [deriv_cosh, sinh_pos_iff] }

@[simp] lemma cosh_le_cosh : cosh x ≤ cosh y ↔ |x| ≤ |y| :=
cosh_abs x ▸ cosh_abs y ▸ cosh_strict_mono_on.le_iff_le (_root_.abs_nonneg x) (_root_.abs_nonneg y)

@[simp] lemma cosh_lt_cosh : cosh x < cosh y ↔ |x| < |y| :=
lt_iff_lt_of_le_iff_le cosh_le_cosh

@[simp] lemma one_le_cosh (x : ℝ) : 1 ≤ cosh x :=
cosh_zero ▸ cosh_le_cosh.2 (by simp only [_root_.abs_zero, _root_.abs_nonneg])

@[simp] lemma one_lt_cosh : 1 < cosh x ↔ x ≠ 0 :=
cosh_zero ▸ cosh_lt_cosh.trans (by simp only [_root_.abs_zero, abs_pos])

lemma sinh_sub_id_strict_mono : strict_mono (λ x, sinh x - x) :=
begin
  refine strict_mono_of_odd_strict_mono_on_nonneg (λ x, by simp) _,
  refine (convex_Ici _).strict_mono_on_of_deriv_pos _ (λ x hx, _),
  { exact (continuous_sinh.sub continuous_id).continuous_on },
  { rw [interior_Ici, mem_Ioi] at hx,
    rw [deriv_sub, deriv_sinh, deriv_id'', sub_pos, one_lt_cosh],
    exacts [hx.ne', differentiable_at_sinh, differentiable_at_id] }
end

@[simp] lemma self_le_sinh_iff : x ≤ sinh x ↔ 0 ≤ x :=
calc x ≤ sinh x ↔ sinh 0 - 0 ≤ sinh x - x : by simp
... ↔ 0 ≤ x : sinh_sub_id_strict_mono.le_iff_le

@[simp] lemma sinh_le_self_iff : sinh x ≤ x ↔ x ≤ 0 :=
calc sinh x ≤ x ↔ sinh x - x ≤ sinh 0 - 0 : by simp
... ↔ x ≤ 0 : sinh_sub_id_strict_mono.le_iff_le

@[simp] lemma self_lt_sinh_iff : x < sinh x ↔ 0 < x :=
lt_iff_lt_of_le_iff_le sinh_le_self_iff

@[simp] lemma sinh_lt_self_iff : sinh x < x ↔ x < 0 :=
lt_iff_lt_of_le_iff_le self_le_sinh_iff

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_add_comm_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


import ..lectures.love01_definitions_and_statements_demo

/-! # LoVe Exercise 1: Definitions and Statements

Replace the placeholders (e.g., `:= sorry`) with your solutions. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Truncated Subtraction

1.1. Define the function `sub` that implements truncated subtraction on natural
numbers by recursion. "Truncated" means that results that mathematically would
be negative are represented by 0. For example:

    `sub 7 2 = 5`
    `sub 2 7 = 0` -/

def sub : ℕ → ℕ → ℕ
| m            0            := m
| 0            _            := 0
| (nat.succ m) (nat.succ n) := sub m n

/-! 1.2. Check that your function works as expected. -/

#eval sub 0 0   -- expected: 0
#eval sub 0 1   -- expected: 0
#eval sub 0 7   -- expected: 0
#eval sub 1 0   -- expected: 1
#eval sub 1 1   -- expected: 0
#eval sub 3 0   -- expected: 3
#eval sub 2 7   -- expected: 0
#eval sub 3 1   -- expected: 2
#eval sub 3 3   -- expected: 0
#eval sub 3 7   -- expected: 0
#eval sub 7 2   -- expected: 5

/-! ## Question 2: Arithmetic Expressions

Consider the type `aexp` from the lecture and the function `eval` that
computes the value of an expression. You will find the definitions in the file
`love01_definitions_and_statements_demo.lean`. One way to find them quickly is
to

1. hold the Control (on Linux and Windows) or Command (on macOS) key pressed;
2. move the cursor to the identifier `aexp` or `eval`;
3. click the identifier. -/

#check aexp
#check eval

/-! 2.1. Test that `eval` behaves as expected. Make sure to exercise each
constructor at least once. You can use the following environment in your tests.
What happens if you divide by zero?

Make sure to use `#eval`. For technical reasons, `#reduce` does not work well
here. Note that `#eval` (Lean's evaluation command) and `eval` (our evaluation
function on `aexp`) are unrelated. -/

def some_env : string → ℤ
| "x" := 3
| "y" := 17
| _   := 201

#eval eval some_env (aexp.var "x")   -- expected: 3
#eval eval some_env (aexp.num 12)                              -- expected: 12
#eval eval some_env (aexp.add (aexp.var "x") (aexp.var "y"))   -- expected: 20
#eval eval some_env (aexp.sub (aexp.num 5) (aexp.var "y"))     -- expected: -12
#eval eval some_env (aexp.mul (aexp.num 11) (aexp.var "z"))    -- expected: 2211
#eval eval some_env (aexp.div (aexp.num 2) (aexp.num 0))       -- expected: 0

/-! 2.2. The following function simplifies arithmetic expressions involving
addition. It simplifies `0 + e` and `e + 0` to `e`. Complete the definition so
that it also simplifies expressions involving the other three binary
operators. -/

def simplify : aexp → aexp
| (aexp.add (aexp.num 0) e₂) := simplify e₂
| (aexp.add e₁ (aexp.num 0)) := simplify e₁
| (aexp.sub e₁ (aexp.num 0)) := simplify e₁
| (aexp.mul (aexp.num 0) e₂) := aexp.num 0
| (aexp.mul e₁ (aexp.num 0)) := aexp.num 0
| (aexp.mul (aexp.num 1) e₂) := simplify e₂
| (aexp.mul e₁ (aexp.num 1)) := simplify e₁
| (aexp.div (aexp.num 0) e₂) := aexp.num 0
| (aexp.div e₁ (aexp.num 0)) := aexp.num 0
| (aexp.div e₁ (aexp.num 1)) := simplify e₁
-- catch-all cases below
| (aexp.num i)               := aexp.num i
| (aexp.var x)               := aexp.var x
| (aexp.add e₁ e₂)           := aexp.add (simplify e₁) (simplify e₂)
| (aexp.sub e₁ e₂)           := aexp.sub (simplify e₁) (simplify e₂)
| (aexp.mul e₁ e₂)           := aexp.mul (simplify e₁) (simplify e₂)
| (aexp.div e₁ e₂)           := aexp.div (simplify e₁) (simplify e₂)

/-! 2.3. Is the `simplify` function correct? In fact, what would it mean for it
to be correct or not? Intuitively, for `simplify` to be correct, it must
return an arithmetic expression that yields the same numeric value when
evaluated as the original expression.

Given an environment `env` and an expression `e`, state (without proving it)
the property that the value of `e` after simplification is the same as the
value of `e` before. -/

lemma simplify_correct (env : string → ℤ) (e : aexp) :
  eval env (simplify e) = eval env e :=
sorry

/-! ## Question 3: λ-Terms

3.1. Complete the following definitions, by replacing the `sorry` markers by
terms of the expected type.

Hint: A procedure for doing so systematically is described in Section 1.1.4 of
the Hitchhiker's Guide. As explained there, you can use `_` as a placeholder
while constructing a term. By hovering over `_`, you will see the current
logical context. -/

def I : α → α :=
λa, a

def K : α → β → α :=
λa b, a

def C : (α → β → γ) → β → α → γ :=
λg b a, g a b

def proj_1st : α → α → α :=
λx y, x

/-! Please give a different answer than for `proj_1st`. -/

def proj_2nd : α → α → α :=
λx y, y

def some_nonsense : (α → β → γ) → α → (α → γ) → β → γ :=
λg a f b, g a b

/-! 3.2. Show the typing derivation for your definition of `C` above, on paper
or using ASCII or Unicode art. You might find the characters `–` (to draw
horizontal bars) and `⊢` useful. -/

/-! Let `D` := `g : α → β → γ, b : β, a : α`. We have

    –––––––––––––––––– Var    –––––––––– Var
    D ⊢ g : α → β → γ         D ⊢ a : α
    –––––––––––––––––––––––––––––––––––– App    –––––––––– Var
    D ⊢ g a : β → γ                             D ⊢ b : β
    –––––––––––––––––––––––––––––––––––––––––––––––––––––– App
    D ⊢ g a b : γ
    ––––––––––––––––––––––––––––––––––––––––––– Lam
    g : α → β → γ, b : β ⊢ (λa : α, g a b) : α → γ
    –––––––––––––––––––––––––––––––––––––––––––––– Lam
    g : α → β → γ ⊢ (λ(b : β) (a : α), g a b) : β → α → γ
    ––––––––––––––––––––––––––––––––––––––––––––––– Lam
    ⊢ (λ(g : α → β → γ) (b : β) (a : α), g a b) : (α → β → γ) → β → α → γ -/

end LoVe


import game.order.level06
import data.real.irrational

open real

namespace xena -- hide

/-
# Chapter 2 : Order

## Level 7

Prove by example that there exist pairs of real numbers
$a$ and $b$ such that $a \in \mathbb{R} \setminus \mathbb{Q}$,
$b \in \mathbb{R} \setminus \mathbb{Q}$,
but their sum $a + b$ is a rational number, $(a+b) \in \mathbb{Q}$.
You may use this result in the Lean mathlib library:

`irrational_sqrt_two : irrational (sqrt 2)`

-/

/- Axiom : irrational_sqrt_two : irrational (sqrt 2)
-/

/- Lemma
Not true that for any $a$, $b$, irrational numbers, the sum is
also an irrational number.
-/
theorem not_sum_irrational :
    ¬ ( ∀ (a b : ℝ), irrational a →  irrational b → irrational (a+b) ) :=
begin
  intro H,
  have H2 := H (sqrt 2) (-sqrt 2),
  have H3 := H2 irrational_sqrt_two (irrational_neg_iff.2 irrational_sqrt_two),
  apply H3,
  existsi (0 : ℚ),
  simp, done
end

end xena -- hide


import Mynat.MulAdv

namespace mynat

def myle (a b : mynat) :=  ∃ (c : mynat), b = a + c
instance : LE mynat where
  le := myle
theorem le_iff_exists_add (a b : mynat) : a ≤ b ↔ ∃ (c : mynat), b = a + c := Iff.rfl

theorem one_add_le_self (x : mynat) : x ≤ 1 + x := by
  rw [le_iff_exists_add]
  exists 1
  rw [add_comm]

theorem le_refl (x : mynat) : x ≤ x :=
  Exists.intro 0 rfl

-- attribute [rfl] mynat.le_refl
-- Why doesn't it work?

theorem le_succ (a b : mynat) : a ≤ b → a ≤ (succ b) := by
  intro h
  cases h with
  | intro c hc =>
    exists succ c
    rw [add_succ]
    rw [hc]

theorem zero_le (a : mynat) : 0 ≤ a := by
  rw [le_iff_exists_add]
  exists a
  rw [zero_add]

theorem le_trans (a b c : mynat) (hab : a ≤ b) (hbc : b ≤ c) : a ≤ c := by
  rw [le_iff_exists_add] at hab hbc
  cases hab with
  | intro d hd =>
    cases hbc with
    | intro e he =>
      rw [hd] at he
      exists d + e
      rw [← add_assoc]
      exact he

theorem le_antisymm (a b : mynat) (hab : a ≤ b) (hba : b ≤ a) : a = b := by
  cases hab with
  | intro c hc =>
    cases hba with
    | intro d hd =>
      rw [hc] at hd
      conv at hd =>
        lhs
        rw [← add_zero a]
      rw [add_assoc] at hd
      have halc := (add_left_cancel a 0 (c+d)) hd
      have halcsym := Eq.symm halc
      have hcez := add_right_eq_zero halcsym
      rw [hcez] at hc
      rw [add_zero] at hc
      exact Eq.symm hc

theorem le_zero (a : mynat) (h : a ≤ 0) : a = 0 := by
  have hh := zero_le a
  exact le_antisymm a 0 h hh

theorem succ_le_succ (a b : mynat) (h : a ≤ b) : succ a ≤ succ b := by
  cases h with
  | intro c hc =>
    exists c
    rw [hc]
    rw [succ_add]

theorem le_total (a b : mynat) : a ≤ b ∨ b ≤ a := by
  cases b
  case zero =>
    apply Or.intro_right
    exact zero_le a
  case succ b' =>
    have hind := le_total a b'
    cases hind
    case inl h =>
      apply Or.intro_left
      cases h with
      | intro c hc =>
        exists succ c
        rw [add_succ]
        rw [hc]
    case inr h =>
      cases h with
      | intro c hc =>
        cases c
        case zero =>
          rw [mynat_zero_eq_zero] at hc
          apply Or.intro_left
          exists 1
          rw [hc]
          rw [add_assoc]
          rw [zero_add]
          exact succ_eq_add_one b'
        case succ c' =>
          apply Or.intro_right
          exists c'
          rw [hc]
          rw [succ_add]
          rw [add_succ]

theorem le_succ_self (a : mynat) : a ≤ succ a := by
  exists 1

theorem add_le_add_right {a b : mynat} : a ≤ b → ∀ t, (a + t) ≤ (b + t) := by
  intro h
  cases h with
  | intro c hc =>
    intro t
    exists c
    rw [hc]
    rw [add_assoc]
    rw [add_assoc]
    rw [add_comm c t]

theorem le_of_succ_le_succ (a b : mynat) : succ a ≤ succ b → a ≤ b := by
  intro h
  cases h with
  | intro c hc =>
    exists c
    rw [succ_eq_add_one] at hc
    rw [succ_eq_add_one] at hc
    rw [add_comm b 1] at hc
    rw [add_comm a 1] at hc
    rw [add_assoc] at hc
    exact add_left_cancel 1 b (a + c) hc

theorem not_succ_le_self (a : mynat) : ¬ (succ a ≤ a) := by
  intro h
  cases h with
  | intro c hc =>
    rw [succ_eq_add_one] at hc
    conv at hc =>
      lhs
      rw [← add_zero a]
    rw [add_assoc] at hc
    have hd := add_left_cancel a 0 (1 + c) hc
    rw [add_comm] at hd
    rw [← succ_eq_add_one] at hd
    have hnd := zero_ne_succ c
    exact hnd hd

theorem add_le_add_left {a b : mynat} (h : a ≤ b) (t : mynat) :
  t + a ≤ t + b := by
  cases h with
  | intro c hc =>
    exists c
    rw [hc]
    rw [← add_assoc]

theorem lt_aux_one (a b : mynat) : a ≤ b ∧ ¬ (b ≤ a) → succ a ≤ b := by
  intro h
  have h1 := h.left
  have h2 := h.right
  cases h1 with
  | intro c hc =>
    cases c
    case zero =>
      rw [mynat_zero_eq_zero] at hc
      -- by contradiction
      rw [add_zero] at hc
      rw [hc] at h2
      apply False.elim
      exact h2 (le_refl a)
    case succ c' =>
      exists c'
      rw [succ_add]
      rw [add_succ] at hc
      exact hc

theorem lt_aux_two (a b : mynat) : succ a ≤ b → a ≤ b ∧ ¬ (b ≤ a) := by
  intro h
  cases h with
  | intro c hc =>
    apply And.intro
    case intro.left =>
      exists succ c
      rw [add_succ]
      rw [succ_add] at hc
      exact hc
    case intro.right =>
      intro hh
      rw [hc] at hh
      rw [succ_add] at hh
      rw [← add_succ] at hh
      cases hh with
      | intro d hd =>
        conv at hd =>
          lhs
          rw [← add_zero a]
        rw [add_assoc] at hd
        have hfalse := add_left_cancel a 0 (succ c + d) hd
        have hsz := add_right_eq_zero (Eq.symm hfalse)
        exact zero_ne_succ c (Eq.symm hsz)

def mylt (a b : mynat) := a ≤ b ∧ ¬ (b ≤ a)
-- incantation so that we can use `<` notation:
instance : LT mynat := ⟨mylt⟩
theorem lt_def (a b : mynat) : a < b ↔ a ≤ b ∧ ¬ (b ≤ a) := Iff.rfl

theorem lt_iff_succ_le (a b : mynat) : a < b ↔ succ a ≤ b := by
  rw [lt_def]
  apply Iff.intro
  . apply lt_aux_one
  . apply lt_aux_two

end mynat


/-
Copyright (c) 2021 Manuel Candales. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Manuel Candales
-/
import data.real.basic
import data.real.sqrt

open real

/-!
# IMO 2008 Q4
Find all functions `f : (0,∞) → (0,∞)` (so, `f` is a function from the positive real
numbers to the positive real numbers) such that
      ```
      (f(w)^2 + f(x)^2)/(f(y^2) + f(z^2)) = (w^2 + x^2)/(y^2 + z^2)
      ```
for all positive real numbers `w`, `x`, `y`, `z`, satisfying `wx = yz`.

# Solution
The desired theorem is that either `f = λ x, x` or `f = λ x, 1/x`
-/

theorem imo2008_q4
  (f : ℝ → ℝ)
  (H₁ : ∀ x > 0, f(x) > 0) :
  (∀ w x y z : ℝ, 0 < w → 0 < x → 0 < y → 0 < z → w*x = y*z →
    (f(w)^2 + f(x)^2)/(f(y^2) + f(z^2)) = (w^2 + x^2)/(y^2 + z^2)) ↔
  ((∀ x > 0, f(x) = x) ∨ (∀ x > 0, f(x) = 1/x)) :=
begin
  split, swap,
  -- proof that f(x) = x and f(x) = 1/x satisfy the condition
  { rintros (h | h),
    { intros w x y z hw hx hy hz hprod,
      rw [(h w hw), (h x hx), (h (y^2) (pow_pos hy 2)), (h (z^2) (pow_pos hz 2))] },
    { intros w x y z hw hx hy hz hprod,
      rw [(h w hw), (h x hx), (h (y^2) (pow_pos hy 2)), (h (z^2) (pow_pos hz 2))],
      have hy2z2 : y^2 + z^2 ≠ 0 := ne_of_gt (add_pos (pow_pos hy 2) (pow_pos hz 2)),
      have hz2y2 : z^2 + y^2 ≠ 0 := ne_of_gt (add_pos (pow_pos hz 2) (pow_pos hy 2)),
      have hp2 : w^2 * x^2 = y^2 * z^2, { rw [(mul_pow w x 2).symm, (mul_pow y z 2).symm, hprod] },
      field_simp [(ne_of_gt hw), (ne_of_gt hx), (ne_of_gt hy), (ne_of_gt hz), hy2z2, hz2y2, hp2],
      ring } },

  -- proof that the only solutions are f(x) = x or f(x) = 1/x
  intro H₂,
  have h₀ : f(1) ≠ 0, { specialize H₁ 1 zero_lt_one, exact ne_of_gt H₁ },

  have h₁ : f(1) = 1,
  { specialize H₂ 1 1 1 1 zero_lt_one zero_lt_one zero_lt_one zero_lt_one rfl,
    norm_num at H₂,
    rw [(two_mul (f(1)^2)).symm, (two_mul (f 1)).symm] at H₂,
    rw mul_div_mul_left (f(1)^2) (f 1) two_ne_zero at H₂,
    rwa ← (div_eq_iff h₀).mpr (pow_two (f 1)) },

  have h₂ : ∀ x > 0, (f x - x) * (f x - 1/x) = 0,
  { intros x hx,
    have h1xss : 1 * x = (sqrt x) * (sqrt x), { rw [one_mul, mul_self_sqrt (le_of_lt hx)] },
    specialize H₂ 1 x (sqrt x) (sqrt x) zero_lt_one hx (sqrt_pos.mpr hx) (sqrt_pos.mpr hx) h1xss,
    rw [h₁, (one_pow 2), (sqr_sqrt (le_of_lt hx)), (two_mul (f x)).symm, (two_mul x).symm] at H₂,
    have hx_ne_0 : x ≠ 0 := ne_of_gt hx,
    have hfx_ne_0 : (f x) ≠ 0, { specialize H₁ x hx, exact ne_of_gt H₁ },
    field_simp at H₂,

    have h1 : (2 * x) * ((f x - x) * (f x - 1/x)) = 0,
    { calc  (2*x) * ((f x - x) * (f x - 1/x))
          = 2 * (f x - x) * (x * f x - x * 1/x)   : by ring
      ... = 2 * (f x - x) * (x * f x - 1)         : by rw (mul_div_cancel_left 1 hx_ne_0)
      ... = ((1+f(x)^2)*(2*x) - (1+x^2)*(2*f(x))) : by ring
      ... = 0                                     : sub_eq_zero.mpr H₂ },

    have h2x_ne_0 : 2*x ≠ 0 := mul_ne_zero two_ne_zero hx_ne_0,

    calc  ((f x - x) * (f x - 1/x))
        = (2*x) * ((f x - x) * (f x - 1/x)) / (2*x) : (mul_div_cancel_left _ h2x_ne_0).symm
    ... = 0                                         : by { rw h1, exact zero_div (2*x) } },

  have h₃ : ∀ x > 0, f(x) = x ∨ f(x) = 1/x,
  { rintros x hx,
    obtain h := zero_eq_mul.mp (h₂ x hx).symm,
    cases h with hp hq,
    left, linarith [hp],
    right, linarith [hq] },

  by_contradiction,
  obtain ⟨hp₁, hq₁⟩ := not_or_distrib.mp h,

  obtain ⟨a, hq₂⟩ := not_forall.mp hq₁,
  obtain ⟨ha, hq₃⟩ := not_imp.mp hq₂,
  obtain hq₄ := or.resolve_right (h₃ a ha) hq₃,
  -- f(a) ≠ 1/a, f(a) = a

  obtain ⟨b, hp₂⟩ := not_forall.mp hp₁,
  obtain ⟨hb, hp₃⟩ := not_imp.mp hp₂,
  obtain hp₄ := or.resolve_left (h₃ b hb) hp₃,
  -- f(b) ≠ b, f(b) = 1/b

  have hab : a*b > 0 := mul_pos ha hb,
  have habss : a*b = sqrt(a*b) * sqrt(a*b) := (mul_self_sqrt (le_of_lt hab)).symm,

  specialize H₂ a b (sqrt (a*b)) (sqrt (a*b)) ha hb (sqrt_pos.mpr hab) (sqrt_pos.mpr hab) habss,
  rw [(sqr_sqrt (le_of_lt hab)), (two_mul (f (a*b))).symm, (two_mul (a*b)).symm] at H₂,
  rw [hp₄, hq₄] at H₂,

  have h2ab_ne_0 : 2*(a*b) ≠ 0 := mul_ne_zero two_ne_zero (ne_of_gt hab),

  specialize h₃ (a*b) hab,
  cases h₃ with hab₁ hab₂,

  -- f(ab) = ab → b^4 = 1 → b = 1 → f(b) = b → false
  { rw hab₁ at H₂, field_simp at H₂,
    obtain hb₁ := or.resolve_right H₂ h2ab_ne_0,
    field_simp [ne_of_gt hb] at hb₁,
    rw (show b^2 * b^2 = (b^2)^2, by ring) at hb₁,
    have hb₂ : sqrt 1 = b^2 := (sqrt_eq_iff_sqr_eq zero_le_one (pow_two_nonneg _)).mpr hb₁.symm,
    rw sqrt_one at hb₂,
    have hb₃ : sqrt 1 = b := (sqrt_eq_iff_sqr_eq zero_le_one (le_of_lt hb)).mpr hb₂.symm,
    rw sqrt_one at hb₃,
    rw ← hb₃ at hp₃, exact hp₃ h₁ },

  -- f(ab) = 1/ab → a^4 = 1 → a = 1 → f(a) = 1/a → false
  { have hb_ne_0 : b ≠ 0 := ne_of_gt hb,
    rw hab₂ at H₂, field_simp at H₂,
    rw ← sub_eq_zero at H₂,
    rw (show (a^2*b^2+1)*(a*b)*(2*(a*b)) - (a^2+b^2)*(b^2*2) = 2*(b^4)*(a^4-1), by ring) at H₂,
    have h2b4_ne_0 : 2*(b^4) ≠ 0 := mul_ne_zero two_ne_zero (pow_ne_zero 4 hb_ne_0),
    obtain ha₁ := or.resolve_left (zero_eq_mul.mp H₂.symm) h2b4_ne_0,
    rw (show a^4-1 = (a^2+1)*(a^2-1), by ring) at ha₁,
    have h2a1_ne_0 : a^2+1 ≠ 0 := ne_of_gt (add_pos (pow_pos ha 2) zero_lt_one),
    obtain ha₂ := or.resolve_left (zero_eq_mul.mp ha₁.symm) h2a1_ne_0,
    rw (show a^2-1 = (a+1)*(a-1), by ring) at ha₂,
    have ha1_ne_0 : a + 1 ≠ 0 := ne_of_gt (add_pos ha zero_lt_one),
    obtain ha₃ := or.resolve_left (zero_eq_mul.mp ha₂.symm) ha1_ne_0,
    rw sub_eq_zero at ha₃,
    rw ha₃ at hq₃, norm_num at hq₃ },
end


import linear_algebra.basic
import data.fintype.basic
import algebra.big_operators
import data.finset algebra.big_operators
import data.set.function
import data.equiv.basic
open finset
open equiv function fintype finset
universes u v w
namespace technical
notation `Σ` := finset.sum finset.univ
variables {α : Type u} {β : Type v}
lemma finset.prod_univ_perm [fintype α] [comm_monoid β] {f : α → β} (σ : perm α) :
  (univ : finset α).prod f = univ.prod (λ z, f (σ z)) :=
eq.symm $ prod_bij (λ z _, σ z) (λ _ _, mem_univ _) (λ _ _, rfl)
  (λ _ _ _ _ H, σ.injective H) (λ b _, ⟨σ⁻¹ b, mem_univ _, by simp⟩)
end technical

def Sum {R : Type u}[add_comm_monoid R]{X : Type v}[fintype X](g : X → R) : R  := finset.sum (finset.univ) g

lemma Sum_permutation  {R :Type u}[add_comm_monoid R]{X : Type v}[fintype X](g : X → R)(σ : equiv.perm X )
: finset.sum (finset.univ) g =   finset.sum (finset.univ) (λ z, g (σ z))  :=   @technical.finset.prod_univ_perm _ (multiplicative R) _ _ g σ

lemma Sum_add {R : Type} [ring R]{X : Type}[fintype X] (g : X → R)(h : X → R)  :  Σ  (h+g) = Σ  h + Σ  g  := begin
    exact multiset.sum_map_add,  ---
end
#check multiset.sum_map_sum_map

lemma Sum.left_mul {R : Type} [ring  R]{X : Type}[fintype X] (g : X → R)  (r : R):  Σ (r •  g) = r • Σ g  := begin
    exact multiset.sum_map_mul_left,
end

lemma Sum.left_right {R : Type} [add_comm_monoid R]{X : Type}[fintype X] (g : X → R)(r : R) :  Σ  (r •   g ) =  (Σ  g)   := begin
    rw mul_comm, exact Sum.left_mul g r,
end
lemma Sum.morp {R : Type} [add_comm_monoid R] (R' : Type) [add_comm_monoid R']
{X : Type}[fintype X] (g : X → R) (φ : R →+ R') : φ ( Σ   g ) =  Σ (λ t, φ  ( g t) )    := begin
    exact add_monoid_hom.map_sum φ g univ,
end

def Sum' (R : Type)[hyp1 : comm_ring R](X :Type)[hyp2 : fintype X] : (X → R) →ₗ[R] R := { to_fun := λ g, Σ g,
  add := begin intros, rw Sum_add,end,
  smul := begin intros, rw Sum.left_mul, exact rfl, end }


/-
Copyright (c) 2016 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad

! This file was ported from Lean 3 source module data.int.order.units
! leanprover-community/mathlib commit d012cd09a9b256d870751284dd6a29882b0be105
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Int.Order.Basic
import Mathlib.Data.Int.Units
import Mathlib.Algebra.GroupPower.Order

/-!
# Lemmas about units in `ℤ`, which interact with the order structure.
-/

namespace Int

theorem isUnit_sq {a : ℤ} (ha : IsUnit a) : a ^ 2 = 1 := by rw [sq, isUnit_mul_self ha]
#align int.is_unit_sq Int.isUnit_sq

@[simp]
theorem units_sq (u : ℤˣ) : u ^ 2 = 1 := by
  rw [Units.ext_iff, Units.val_pow_eq_pow_val, Units.val_one, isUnit_sq u.isUnit]
#align int.units_sq Int.units_sq

alias units_sq ← units_pow_two
#align int.units_pow_two Int.units_pow_two

@[simp]
theorem units_mul_self (u : ℤˣ) : u * u = 1 := by rw [← sq, units_sq]
#align int.units_mul_self Int.units_mul_self

@[simp]
theorem units_inv_eq_self (u : ℤˣ) : u⁻¹ = u := by rw [inv_eq_iff_mul_eq_one, units_mul_self]
#align int.units_inv_eq_self Int.units_inv_eq_self

-- `units.coe_mul` is a "wrong turn" for the simplifier, this undoes it and simplifies further
@[simp]
theorem units_coe_mul_self (u : ℤˣ) : (u * u : ℤ) = 1 := by
  rw [← Units.val_mul, units_mul_self, Units.val_one]
#align int.units_coe_mul_self Int.units_coe_mul_self

@[simp]
theorem neg_one_pow_ne_zero {n : ℕ} : (-1 : ℤ) ^ n ≠ 0 :=
  pow_ne_zero _ (abs_pos.mp (by simp))
#align int.neg_one_pow_ne_zero Int.neg_one_pow_ne_zero

theorem sq_eq_one_of_sq_lt_four {x : ℤ} (h1 : x ^ 2 < 4) (h2 : x ≠ 0) : x ^ 2 = 1 :=
  sq_eq_one_iff.mpr
    ((abs_eq (zero_le_one' ℤ)).mp
      (le_antisymm (lt_add_one_iff.mp (abs_lt_of_sq_lt_sq h1 zero_le_two))
        (sub_one_lt_iff.mp (abs_pos.mpr h2))))
#align int.sq_eq_one_of_sq_lt_four Int.sq_eq_one_of_sq_lt_four

theorem sq_eq_one_of_sq_le_three {x : ℤ} (h1 : x ^ 2 ≤ 3) (h2 : x ≠ 0) : x ^ 2 = 1 :=
  sq_eq_one_of_sq_lt_four (lt_of_le_of_lt h1 (lt_add_one (3 : ℤ))) h2
#align int.sq_eq_one_of_sq_le_three Int.sq_eq_one_of_sq_le_three

theorem units_pow_eq_pow_mod_two (u : ℤˣ) (n : ℕ) : u ^ n = u ^ (n % 2) := by
  conv =>
      lhs
      rw [← Nat.mod_add_div n 2];
      rw [pow_add, pow_mul, units_sq, one_pow, mul_one]
#align int.units_pow_eq_pow_mod_two Int.units_pow_eq_pow_mod_two

end Int


import MyNat.Definition
import MyNat.Addition
import AdvancedAdditionWorld.Level1 -- succ_inj
import AdvancedAdditionWorld.Level10 -- zero_ne_succ
namespace MyNat
open MyNat

/-!

# Advanced Addition World

## Level 13: `ne_succ_self`

The last level in Advanced Addition World is the statement
that `n ≠ succ n`.

## Lemma
For any natural number `n`, we have ` n ≠ succ n`.
-/
lemma ne_succ_self (n : MyNat) : n ≠ succ n := by
  induction n with
  | zero =>
    apply zero_ne_succ
  | succ n ih =>
    intro hs
    apply ih
    apply succ_inj
    assumption

/-!
Well that's a wrap on Advanced Addition World !

You can now move on to Advanced Multiplication World
(after first doing [Multiplication World](../MultiplicationWorld.lean.md), if you didn't do it already).
-/


import data.fintype.basic
import data.set.function
import .sum_tools
import data.equiv.basic
import linear_algebra.bilinear_form
universe variables u v w
open finset
open_locale big_operators
/--
#   Je n'aime pas du tout l'utilisation du produit scalaire car je prefere avoir une notion spécifique
-/
variables {G :Type u} {R : Type v} [group G] [comm_ring R][fintype G]
/-!
    We define a `scalar_product` on function `G → R`
-/
def bilinear   (φ ψ : G → R  ) :=  ∑ t, (φ t) *  (ψ t⁻¹)

variables {α : Type u} {β : Type v}
variables (φ ψ :  G → R  )
notation ` ⁅  ` φ ` | ` ψ ` ⁆ ` := bilinear φ ψ
/--
     `《 φ + γ | ψ 》 = 《 φ | ψ 》 + 《 γ  | ψ 》`
-/
@[simp]theorem add_first  (φ γ ψ  : G → R  ) : ⁅ φ + γ | ψ ⁆  = ⁅  φ | ψ ⁆  + ⁅  γ  | ψ ⁆  :=
begin
      unfold bilinear, erw ← multiset.sum_map_add,  --- ?
      congr,funext, erw ← right_distrib _ _ (ψ t⁻¹),
      exact rfl,
end
/--
     `《 r • φ  | ψ 》 = r • 《 φ | ψ 》`
-/
@[simp]theorem smul_first  (r : R) (φ ψ   : G → R  ) : ⁅  r • φ  | ψ ⁆  = r • ⁅ φ | ψ ⁆  := begin
      unfold bilinear,
     erw  finset.smul_sum,congr,funext, erw mul_assoc, exact rfl,
end
/--
     `⁅  ψ  |  φ ⁆  =  ⁅ φ | ψ ⁆`
-/
@[simp]theorem symm_bilinear (φ ψ   : G → R  ) : ⁅  ψ  |  φ ⁆  =  ⁅ φ | ψ ⁆ :=
begin
    unfold bilinear,
    rw sum_univ_perm _ (inv_equiv), congr,
    funext, unfold inv_equiv, dsimp, rw inv_inv,rw mul_comm,
end
/--
    `⁅ φ  | ψ + γ⁆  = ⁅  φ | ψ ⁆  + ⁅  φ   | γ  ⁆`
-/
@[simp]theorem add_snd  (φ ψ γ  : G → R  ) : ⁅ φ  | ψ + γ⁆  = ⁅  φ | ψ ⁆  + ⁅  φ   | γ  ⁆  :=begin
    rw symm_bilinear, rw add_first, simp,
end
/--
    `⁅   φ  | r • ψ ⁆  = r • ⁅ φ | ψ ⁆
-/
@[simp]theorem smul_snd  (r : R) (φ ψ   : G → R  ) : ⁅   φ  | r • ψ ⁆  = r • ⁅ φ | ψ ⁆ := begin
    rw symm_bilinear, rw smul_first, rw symm_bilinear,
end

def scalar_product (G : Type u)[group G] [fintype G] (R : Type v) [group G] [comm_ring R] : bilin_form  R (G → R) := {
  bilin             := @bilinear G R _ _ _,
  bilin_add_left    := @add_first G R _ _ _,
  bilin_smul_left   := @smul_first G R _ _ _,
  bilin_add_right   := @add_snd G R _ _ _,
  bilin_smul_right  := @smul_snd G R _ _ _
}
/--
    `scalar_product G f1 f2 =   ∑ t, (f1 t) *  (f2 t⁻¹) `
-/
lemma scalar_product_ext (G : Type u)[group G] [fintype G] {R : Type v} [group G] [comm_ring R] (f1 f2 : G → R ) :
    scalar_product G R f1 f2 =   ∑ t, (f1 t) *  (f2 t⁻¹) := rfl
open bilin_form
/--
    `scalar_product G f1 f2 = ⁅   f1  | f2 ⁆`
-/
lemma scalar_product_ext' (G : Type u)[group G] [fintype G] (f1 f2 : G → R ) : scalar_product G R f1 f2 =
       ⁅   f1  | f2 ⁆ := rfl
open bilin_form
lemma bilin_sum (X Y : Type w )[fintype X][fintype Y][decidable_eq X][decidable_eq Y]
 (φ_l : X → (G → R ))
 (φ_r : Y → (G → R )) :
 scalar_product G R (∑ x, φ_l x ) (∑ y, φ_r y) = ∑ x, (∑ y, scalar_product G R (φ_l x) (φ_r y)) :=
 begin
  rw map_sum_left,
  congr,
  funext,
  rw map_sum_right,
end
lemma bilin_symm (G : Type u)[group G] [fintype G] (f1 f2 : G → R ) : scalar_product G R f1 f2 = scalar_product G R f2 f1 :=
begin
    rw scalar_product_ext', erw  symm_bilinear,  exact rfl,
end


/-
Copyright (c) 2021 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics

/-!

# Sets in Lean, example sheet 2 : the empty set and the "universal set".

We know what the empty subset of `X` is, and the Lean notation for
it is `∅`, or, if you want to say which type we're the empty subset
of, it's `∅ : set X`.

At the other extreme, the subset of `X` containing all the terms of type `X`
is...well...mathematicians would just call it `X`, but `X` is a type, and
so if we want a set it's called `set.univ : set X`, or just `univ : set X` if
we have opened the `set` namespace. Let's do that now.

-/

open set

/-

## Important

`x ∈ ∅` is *by definition* equal to `false` and `x ∈ univ` is *by definition*
equal to `true`. You can use the `change` tactic to make these changes
if you like. But you don't have to.

## Tactics you will need

You've seen them already. `trivial` proves `⊢ true` and `exfalso`
changes `⊢ P` to `⊢ false`.

-/

-- set up variables
variables
  (X : Type) -- Everything will be a subset of `X`
  (A B C D E : set X) -- A,B,C,D,E are subsets of `X`
  (x y z : X) -- x,y,z are elements of `X` or, more precisely, terms of type `X`

/-

If `x : X` then `x ∈ ∅` is *by definition* `false`, and `x ∈ univ` is
*by definition* `true`. So you can use the `change` tactic to change
between these things, for example if your goal is

```
⊢ x ∈ univ
```

then `change true` will change the goal to

```
⊢ true
```

and you can now prove this goal with `trivial`. However you can prove
it with `trivial` even without `change`ing it.

-/

open set

example : x ∈ (univ : set X) :=
begin
  sorry
end

example : x ∈ (∅ : set X) → false :=
begin
  sorry
end

example : ∀ x : X, x ∈ A → x ∈ (univ : set X) :=
begin
  sorry
end

example : ∀ x : X, x ∈ (∅ : set X) → x ∈ A :=
begin
  sorry
end


import data.fintype.basic order.filter.at_top_bot

lemma subseq_ex_frequent_val_of_finite_range
{β : Type} [fintype β]
(s : ℕ → β) :
∃ (b : β), ∀ n : ℕ, ∃ m : ℕ, m ≥ n ∧ s m = b :=
begin
  by_contra,
  push_neg at h,
  choose ns hns using h,
  let n := finset.univ.sup ns,
  have : n ≥ ns (s n) := finset.le_sup (finset.mem_univ (s n)),
  replace := hns (s n) n this,
  exact this rfl,
end

lemma ex_const_subseq_of_finite_range
{β: Type} [fintype β]
(s : ℕ → β) :
∃ (b : β) (φ : ℕ → ℕ),
strict_mono φ ∧
∀ n : ℕ, s (φ n) = b :=
begin
  rcases subseq_ex_frequent_val_of_finite_range s
    with ⟨b, hb⟩,
  refine ⟨b, _⟩,
  convert filter.extraction_of_frequently_at_top' _,
  rotate,
  {exact λ n, s n = b},
  rotate,
  {refl},
  intro n,
  rcases hb n.succ with ⟨m, hge, heq⟩,
  exact ⟨m, (nat.lt_of_succ_le hge), heq⟩,
end

lemma ex_const_mem_subseq_of_setvalued_seq
{β : Type} [fintype β]
{s : ℕ → finset β}
(hs : ∀ n : ℕ, (s n).nonempty) :
∃ (b : β) (φ : ℕ → ℕ),
strict_mono φ ∧
∀ n : ℕ, b ∈ s (φ n) :=
begin
  choose s' hs' using hs,
  rcases ex_const_subseq_of_finite_range s'
    with ⟨b, φ, hmon, heq⟩,
  refine ⟨b, φ, hmon, _⟩,
  intro n,
  rw [←heq n],
  exact hs' (φ n),
end

-- perhaps compare subseq_forall_of_frequently
lemma subseq_forall_of_frequently'
{α : Type}
{s : ℕ → α}
(p : α → Prop)
(h : ∃ᶠ n in filter.at_top, p (s n)) :
∃ φ : ℕ → ℕ,
strict_mono φ ∧
∀ n : ℕ, p ((s ∘ φ) n) :=
begin
  have : ∀ n : ℕ, ∃ m : ℕ, m ≥ n ∧ p (s m),
  {
    intro n,
    rw [filter.frequently_at_top] at h,
    obtain ⟨m, h₁, h₂⟩ := h n,
    refine ⟨m, h₁, h₂⟩,
  },
  choose φ₁ h₁ using this,
  obtain ⟨φ₂, h₂₁, h₂₂⟩ := filter.strict_mono_subseq_of_id_le (λ n, (h₁ n).1),
  refine ⟨φ₁ ∘ φ₂, h₂₂, _⟩,
  intro n,
  simp only [function.comp_app],
  exact (h₁ (φ₂ n)).2,
end

/- lemma frequently_subseq
{α : Type}
{s : ℕ → α}
(p : α → Prop)
(h : ∀ᶠ n in filter.at_top, p (s n)) : -/


-- we want to use tactics
import tactic
-- we want to use the real numbers
import data.real.basic

/-

# Limits of sequences in Lean

We give the standard `ε`, `N` definition of the limit of a sequence
and prove some theorems about them. The boss level: limit of
products is product of limits.

## Lean level

You need to know the natural number game tactics and a few more too.
Check out the `HINTS.md` file for more information about tactics which
are useful for this sheet. I would say this sheet was of easy
mathematical difficulty and medium lean difficulty.

-/

/-

## lambda (λ) notation for functions

Here's how we define the functions from the naturals to the naturals
sending n to n^2 + 3:

-/

def f : ℕ → ℝ := λ n, n^2+3

/-

Mathematicians might write `n ↦ n^2+3` for this functions; indeed `λ` is
just prefix notation for the infix notation `↦` (i.e. you write it at the
front, not in the middle).

The reason we need to know about function notation for this sheet
is that a sequence `x₀, x₁, x₂, …` of reals on this sheet will
be encoded as a function `f : ℕ → ℝ` sending `0` to `x₀`, `1` to `x₁`
and so on.

## Limit of a sequence.

Here's the definition of the limit of a sequence in Lean.
In VS Code, if you ever hover any `tendsto` *other* than the one actually
being defined, you can see the
definition and the docstring (just above it) formatted nicely.
-/

/-- If `a(n)` is a sequence of reals and `t` is a real, `tendsto a t`
is the assertion that the limit of `a(n)` as `n → ∞` is `t`. -/
def tendsto (a : ℕ → ℝ) (t : ℝ) : Prop :=
∀ ε > 0, ∃ B : ℕ, ∀ n, B ≤ n → |a n - t| < ε

-- the basic API for `tendsto`

/-- If your goal is `tendsto a t` and you want to replace it with
`∀ ε > 0, ∃ B, …` then you can do this with `rw tendsto_def`. -/
theorem tendsto_def {a : ℕ → ℝ} {t : ℝ} :
  tendsto a t ↔ ∀ ε, 0 < ε → ∃ B : ℕ, ∀ n, B ≤ n → |a n - t| < ε :=
begin
  -- true by definition
  refl
end

/-

## The questions

Here are some basic results about limits of sequences.
See if you can fill in the `sorry`s with Lean proofs.
The HINTS.md file in this directory contains some mathematical
sketch proofs and some hints about which tactics might be helpful
in this situation.

-/

/-- The limit of the constant sequence with value 37 is 37. -/
theorem tendsto_thirtyseven (c : ℝ) : tendsto (λ n, 37) 37 :=
begin
  -- start with `rw tendsto_def` so you can see what's going on.
  -- That's the point of `tendsto_def`.
  sorry,
end

/-- The limit of the constant sequence with value `c` is `c`. -/
theorem tendsto_const (c : ℝ) : tendsto (λ n, c) c :=
begin
  sorry,
end

/-- If `a(n)` tends to `t` then `a(n) + c` tends to `t + c` -/
theorem tendsto_add_const {a : ℕ → ℝ} {t : ℝ} (c : ℝ)
  (h : tendsto a t) :
  tendsto (λ n, a n + c) (t + c) :=
begin
  sorry,
end

/-- If `a(n)` tends to `t` then `-a(n)` tends to `-t`.  -/
theorem tendsto_neg {a : ℕ → ℝ} {t : ℝ} (ha : tendsto a t) :
  tendsto (λ n, - a n) (-t) :=
begin
  sorry,
end

/-- If `a(n)` tends to `t` and `b(n)` tends to `u` then `a(n) + b(n)`
tends to `t + u`. -/
theorem tendsto_add {a b : ℕ → ℝ} {t u : ℝ}
  (ha : tendsto a t) (hb : tendsto b u) :
  tendsto (λ n, a n + b n) (t + u) :=
begin
  sorry,
end

/-- If `a(n)` tends to t and `b(n)` tends to `u` then `a(n) - b(n)`
tends to `t - u`. -/
theorem tendsto_sub {a b : ℕ → ℝ} {t u : ℝ}
  (ha : tendsto a t) (hb : tendsto b u) :
  tendsto (λ n, a n - b n) (t - u) :=
begin
  sorry
end

/-- If `a(n)` tends to `t` then `37 * a(n)` tends to `37 * t`-/
theorem tendsto_thirtyseven_mul (a : ℕ → ℝ) (t : ℝ) (h : tendsto a t) :
  tendsto (λ n, 37 * a n) (37 * t) :=
begin
  sorry,
end

/-- If `a(n)` tends to `t` and `c` is a positive constant then
`c * a(n)` tends to `c * t`. -/
theorem tendsto_pos_const_mul {a : ℕ → ℝ} {t : ℝ} (h : tendsto a t)
  {c : ℝ} (hc : 0 < c) : tendsto (λ n, c * a n) (c * t) :=
begin
  sorry,
end

/-- If `a(n)` tends to `t` and `c` is a negative constant then
`c * a(n)` tends to `c * t`. -/
theorem tendsto_neg_const_mul {a : ℕ → ℝ} {t : ℝ} (h : tendsto a t)
  {c : ℝ} (hc : c < 0) : tendsto (λ n, c * a n) (c * t) :=
begin
  sorry,
end

/-- If `a(n)` tends to `t` and `c` is a constant then `c * a(n)` tends
to `c * t`. -/
theorem tendsto_const_mul {a : ℕ → ℝ} {t : ℝ} (c : ℝ) (h : tendsto a t) :
  tendsto (λ n, c * a n) (c * t) :=
begin
  sorry,
end

/-- If `a(n)` tends to `t` and `c` is a constant then `a(n) * c` tends
to `t * c`. -/
theorem tendsto_mul_const {a : ℕ → ℝ} {t : ℝ} (c : ℝ) (h : tendsto a t) :
  tendsto (λ n, a n * c) (t * c) :=
begin
  sorry
end

/-- If `a(n)-b(n)` tends to `t` and `b(n)` tends to `u` then
`a(n)` tends to `t + u`. -/
theorem tendsto_of_tendsto_sub {a b : ℕ → ℝ} {t u : ℝ}
  (h1 : tendsto (λ n, a n - b n) t) (h2 : tendsto b u) :
  tendsto a (t+u) :=
begin
  sorry,
end

/-- If `a(n)` tends to `t` then `a(n)-t` tends to `0`. -/
theorem tendsto_sub_lim {a : ℕ → ℝ} {t : ℝ}
  (h : tendsto a t) : tendsto (λ n, a n - t) 0 :=
begin
  sorry,
end

/-- If `a(n)` and `b(n)` both tend to zero, then their product tends
to zero. -/
theorem tendsto_zero_mul_tendsto_zero
  {a b : ℕ → ℝ} (ha : tendsto a 0) (hb : tendsto b 0) :
  tendsto (λ n, a n * b n) 0 :=
begin
  sorry,
end

/-- If `a(n)` tends to `t` and `b(n)` tends to `u` then
`a(n)*b(n)` tends to `t*u`. -/
theorem tendsto_mul (a b : ℕ → ℝ) (t u : ℝ) (ha : tendsto a t)
  (hb : tendsto b u) : tendsto (λ n, a n * b n) (t * u) :=
begin
  sorry,
end

-- something we never used!
/-- A sequence has at most one limit. -/
theorem tendsto_unique (a : ℕ → ℝ) (s t : ℝ)
  (hs : tendsto a s) (ht : tendsto a t) : s = t :=
begin
  sorry
end


-- various flavours of induction

import  .num_lemmas
----------------------------------------------------------------
open_locale classical
noncomputable theory

universe u

open set

variables {α : Type*}

section numbers

variables (P : ℤ → Prop)

/-- Strong induction (with base case 0) for the nonnegative integers -/
lemma nonneg_int_strong_induction :
  P 0 → (∀ n, 0 < n → (∀ m, 0 ≤ m → m < n → P m) → P n) → (∀ n₀, 0 ≤ n₀ → P n₀) :=
begin
  intros h0 IH n₀ hn₀,
  set Q : ℕ → Prop := λ s, (∀ t, t ≤ s → P t) with hQ,
  suffices : Q (n₀.to_nat),

  have h' := this n₀.to_nat (le_of_eq _),
  rw (int.to_nat_of_nonneg hn₀) at h', from h', refl,

  apply nat.case_strong_induction_on _,
  refine λ t ht, _,
  rw nat.eq_zero_of_le_zero ht, norm_cast, from h0,

  intros n hn t ht,
  cases (nat.eq_zero_or_pos t),
  rw h, norm_cast, from h0,

  apply IH t _,
  intros m h0m hmt,
  have := hn (m.to_nat) _ (m.to_nat) (le_refl _),
  rw (int.to_nat_of_nonneg h0m) at this, from this,

  rw int.to_nat_le,
  rw [←int.coe_nat_le_coe_nat_iff, ←int.coe_nat_add_one_out] at ht, linarith,

  exact int.coe_nat_pos.mpr h,
end

/-- Induction on nonnegative integers with base case 0, and inductive step n-1 → n -/
lemma nonneg_int_induction_minus :
  P 0 → (∀ n, 0 < n → P (n-1) → P n) → (∀ n₀, 0 ≤ n₀ → P n₀) :=
begin
  intros h0 IH,
  apply nonneg_int_strong_induction _ h0,
  from λ n hn0 IHs, IH n hn0 (IHs _ (int.le_sub_one_of_lt hn0) (sub_one_lt _)),
end

/-- Induction on nonnegative integers with base case 0, and inductive step n → n+1 -/
lemma nonneg_int_induction :
  P 0 → (∀ n, 0 ≤ n → P n → P (n+1)) → (∀ n₀, 0 ≤ n₀ → P n₀) :=
begin
  refine λ h IHplus, (nonneg_int_induction_minus P h (λ n hn, _)),
  have := λ hminus, IHplus (n-1) (int.le_sub_one_of_lt hn) hminus,
  norm_num at this, assumption,
end

lemma nat_induction_zero_one (P : ℕ → Prop) (n₀ : ℕ) :
  P 0 → P 1 → (∀ n, 2 ≤ n → P (n-1) → P n) → P n₀ :=
begin
  intros h0 h1 hind,
  induction n₀ with m,
  from h0,
  cases nat.eq_zero_or_pos m,
  rw h, from h1,
  have : 2 ≤ m.succ := by {rw nat.succ_eq_add_one, linarith},
  from hind _ this n₀_ih,
end

/-- Proves that P holds for all elements of a type α by
    strong induction on the value of a nonnegative parameter f on α -/

lemma nonneg_int_strong_induction_param (P : α → Prop) (f : α → ℤ)
(f_nonneg : ∀ a : α, 0 ≤ f a) :
  (∀ a, f a = 0 → P a) → (∀ a : α, 0 < f a → (∀ a', f a' < f a → P a') → P a) → (∀ a, P a) :=
begin
  let Q : ℤ → Prop := λ s, ∀ a, f a = s → P a,
  intros h h',
  suffices : ∀ n₀, 0 ≤ n₀ → Q n₀,
    from λ a, this (f a) (f_nonneg _) _ rfl,
  apply nonneg_int_strong_induction Q h,
  intros n hn hnI a ha,
  refine h' _ (by {rw ha, from hn}) (λ a' ha', _),
  from hnI (f a') (f_nonneg a') (by {rw ←ha ,from ha'}) _ rfl,
end

lemma min_counterexample_nonneg_int_param (P : α → Prop) (f : α → ℤ) (f_nonneg : ∀ a : α, 0 ≤ f a) :
  ¬ (∀ a, P a) → ∃ x, (¬ P x ∧ ∀ x', f x' < f x → P x') :=
begin
  contrapose!,
  refine λ h, nonneg_int_strong_induction_param P f f_nonneg
    (λ a ha, by_contra (λ hn, _))
    (λ a ha h', by_contra (λ hn, (let ⟨a',h₁,h₂⟩ := h _ hn in h₂ (h' _ h₁)))),
  obtain ⟨a', h', -⟩ :=  h _ hn,
  have := f_nonneg a',
    -- linarith should work here before the rw but for some reason it doesn't. Look into this.
  rw ha at h',
  linarith,
end

end numbers


import NBG.SetTheory.Relation.Reflexive

def isSymmetric (R : Class) [Relation R] : Prop := (R ＝ RelInv R)
class Symmetric (R : Class) extends Relation R where
  isSymmetric : isSymmetric R

def isTransitiveRelation (R : Class) [Relation R] : Prop :=
  ∀x y z: Class, ∀_: Set x, ∀_: Set y, ∀_: Set z,
    (＜x, y＞ ∈ R ∧ ＜y, z＞ ∈ R) → ＜x, z＞ ∈ R
class TransitiveRelation (R : Class) extends Relation R where
  isTransitiveRelation : isTransitiveRelation R

def isEquivalence (R : Class) [Relation R] : Prop :=
  isSymmetric R ∧ isTransitiveRelation R
class EqivalenceRelation (R : Class) extends Symmetric R, TransitiveRelation R

theorem IdentityFunctionIsEquivalenceRelation:
  @isEquivalence IdClass ⟨IdClassIsRelation⟩ := sorry

theorem EquivalenceRelationImpDomEqRng (R : Class) [EqivalenceRelation R]:
  Dom R ＝ Rng R := sorry

theorem EquivalenceRelationIsReflexive (R : Class) [EqivalenceRelation R]:
  isReflexive R := sorry

-- Equivalence Class


import ..lovelib

/-! # LoVe Exercise 3: Forward Proofs -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Connectives and Quantifiers

1.1. Supply structured proofs of the following lemmas. -/

lemma I (a : Prop) :
  a → a :=
sorry

lemma K (a b : Prop) :
  a → b → b :=
sorry

lemma C (a b c : Prop) :
  (a → b → c) → b → a → c :=
sorry

lemma proj_1st (a : Prop) :
  a → a → a :=
sorry

/-! Please give a different answer than for `proj_1st`. -/

lemma proj_2nd (a : Prop) :
  a → a → a :=
sorry

lemma some_nonsense (a b c : Prop) :
  (a → b → c) → a → (a → c) → b → c :=
sorry

/-! 1.2. Supply a structured proof of the contraposition rule. -/

lemma contrapositive (a b : Prop) :
  (a → b) → ¬ b → ¬ a :=
sorry

/-! 1.3. Supply a structured proof of the distributivity of `∀` over `∧`. -/

lemma forall_and {α : Type} (p q : α → Prop) :
  (∀x, p x ∧ q x) ↔ (∀x, p x) ∧ (∀x, q x) :=
sorry

/-! 1.4 (**optional**). Reuse, if possible, the lemma `forall_and` from question
1.3 to prove the following instance of the lemma. -/

lemma forall_and_inst {α : Type} (r s : α → α → Prop) :
  (∀x, r x x ∧ s x x) ↔ (∀x, r x x) ∧ (∀x, s x x) :=
sorry

/-! 1.5. Supply a structured proof of the following property, which can be used
pull a `∀`-quantifier past an `∃`-quantifier. -/

lemma forall_exists_of_exists_forall {α : Type} (p : α → α → Prop) :
  (∃x, ∀y, p x y) → (∀y, ∃x, p x y) :=
sorry

/-! ## Question 2: Chain of Equalities

2.1. Write the following proof using `calc`.

      (a + b) * (a + b)
    = a * (a + b) + b * (a + b)
    = a * a + a * b + b * a + b * b
    = a * a + a * b + a * b + b * b
    = a * a + 2 * a * b + b * b

Hint: You might need the tactics `simp` and `cc` and the lemmas `mul_add`,
`add_mul`, and `two_mul`. -/

lemma binomial_square (a b : ℕ) :
  (a + b) * (a + b) = a * a + 2 * a * b + b * b :=
sorry

/-! 2.2 (**optional**). Prove the same argument again, this time as a structured
proof, with `have` steps corresponding to the `calc` equations. Try to reuse as
much of the above proof idea as possible, proceeding mechanically. -/

lemma binomial_square₂ (a b : ℕ) :
  (a + b) * (a + b) = a * a + 2 * a * b + b * b :=
sorry

/-! 2.3. Prove the same lemma again, this time using tactics. -/

lemma binomial_square₃ (a b : ℕ) :
  (a + b) * (a + b) = a * a + 2 * a * b + b * b :=
begin
  sorry
end

/-! ## Question 3 (**optional**): One-Point Rules

3.1 (**optional**). Prove that the following wrong formulation of the one-point
rule for `∀` is inconsistent, using a structured proof. -/

axiom forall.one_point_wrong {α : Type} {t : α} {p : α → Prop} :
  (∀x : α, x = t ∧ p x) ↔ p t

lemma proof_of_false :
  false :=
sorry

/-! 3.2 (**optional**). Prove that the following wrong formulation of the
one-point rule for `∃` is inconsistent, using a tactical or structured proof. -/

axiom exists.one_point_wrong {α : Type} {t : α} {p : α → Prop} :
  (∃x : α, x = t → p x) ↔ p t

lemma proof_of_false₂ :
  false :=
sorry

end LoVe


import data.real.basic
import data.nat.prime

example {x y : ℝ} (h₀ : x ≤ y) (h₁ : ¬ y ≤ x) : x ≤ y ∧ x ≠ y :=
begin
  split,
  { assumption},
  intro h,
  apply h₁,
  rw h,
end

example {x y : ℝ} (h₀ : x ≤ y) (h₁ : ¬ y ≤ x) : x ≤ y ∧ x ≠ y :=
begin
  have h : x ≠ y,
  { contrapose! h₁,
    rw h₁ },
  exact ⟨h₀, h⟩
end

example {x y : ℝ} (h : x ≤ y ∧ x ≠ y) : ¬ y ≤ x :=
begin
  cases h with h1 h2,
  contrapose! h2,
  from le_antisymm h1 h2,
end

example {m n : ℕ} (h : m ∣ n ∧ m ≠ n) :
  m ∣ n ∧ ¬ n ∣ m :=
begin
  cases h with h1 h2,
  split,
  { exact h1,},
  { contrapose! h2,
    exact nat.dvd_antisymm h1 h2},
end

example (x y : ℝ) : (∃ z : ℝ, x < z ∧ z < y) → x < y :=
begin
  rintros ⟨z, xltz, zlty⟩,
  from lt_trans xltz zlty,
end

example {x y : ℝ} (h : x ≤ y) : ¬ y ≤ x ↔ x ≠ y :=
begin
  split,
    contrapose!,
    intro h',
    from le_of_eq h'.symm,

    contrapose!,
    from le_antisymm h,
end

example {x y : ℝ} : x ≤ y ∧ ¬ y ≤ x ↔ x ≤ y ∧ x ≠ y :=
begin
  split,
  { rintros ⟨h1, h2⟩,
    split,
    { from h1,},
    { contrapose! h2,
      from le_of_eq h2.symm,},
  },
  { rintros ⟨h1, h2⟩,
    split,
    { from h1,},
    { contrapose! h2,
      from le_antisymm h1 h2,},
  },
end

theorem aux {x y : ℝ} (h : x^2 + y^2 = 0) : x = 0 :=
begin
  have h' : x^2 = 0,
  { apply le_antisymm,
    { apply le_of_not_gt,
      intro hn,
      have : x^2 + y^2 > y^2 := by linarith,
      have : 0 > y^2 := by linarith,
      have : 0 ≤ y^2 := by apply pow_two_nonneg,
      linarith,},
    { apply pow_two_nonneg,}
  },
  exact pow_eq_zero h'
end

example (x y : ℝ) : x^2 + y^2 = 0 ↔ x = 0 ∧ y = 0 :=
begin
  split,
  { intro h,
    split,
    from aux h,
    rw add_comm at h,
    from aux h,
  },
  { rintro ⟨h1, h2⟩,
    rw [h1, h2],
    norm_num,
  },
end

example (x y : ℝ) : abs (x + 3) < 5 → -8 < x ∧ x < 2 :=
begin
  rw abs_lt,
  intro h,
  split; linarith,
end

theorem not_monotone_iff {f : ℝ → ℝ}:
  ¬ monotone f ↔ ∃ x y, x ≤ y ∧ f x > f y :=
by { rw monotone, push_neg }

example : ¬ monotone (λ x : ℝ, -x) :=
begin
  rw not_monotone_iff,
  use [1, 2],
  norm_num,
end

section
variables {α : Type*} [partial_order α]
variables a b : α

example : a < b ↔ a ≤ b ∧ a ≠ b :=
begin
  rw lt_iff_le_not_le,
  split,
  { rintro ⟨h1 , h2⟩,
    split,
    from h1,
    intro h,
    apply h2,
    from le_of_eq h.symm,
  },
  { rintro ⟨h1, h2⟩,
    split,
    from h1,
    contrapose! h2,
    from le_antisymm h1 h2,
  },
end

end

section
variables {α : Type*} [preorder α]
variables a b c : α

example : ¬ a < a :=
begin
  rw lt_iff_le_not_le,
  rintro ⟨h, hn⟩,
  from hn h,
end

example : a < b → b < c → a < c :=
begin
  simp only [lt_iff_le_not_le], -- ⊢ ⊢ a ≤ b ∧ ¬b ≤ a → b ≤ c ∧ ¬c ≤ b → a ≤ c ∧ ¬c ≤ a
  rintros ⟨aleb, nblea⟩ ⟨blec, ncleb⟩,
  split,
  from le_trans aleb blec,
  contrapose! ncleb,
  from le_trans ncleb aleb,
end

end


/-
Copyright (c) 2022 Cuma Kökmen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Cuma Kökmen, Yury Kudryashov
-/
import measure_theory.integral.circle_integral

/-!
# Integral over a torus in `ℂⁿ`

In this file we define the integral of a function `f : ℂⁿ → E` over a torus
`{z : ℂⁿ | ∀ i, z i ∈ metric.sphere (c i) (R i)}`. In order to do this, we define
`torus_map (c : ℂⁿ) (R θ : ℝⁿ)` to be the point in `ℂⁿ` given by $z_k=c_k+R_ke^{θ_ki}$,
where $i$ is the imaginary unit, then define `torus_integral f c R` as the integral over
the cube $[0, (λ _, 2π)] = \{θ\|∀ k, 0 ≤ θ_k ≤ 2π\}$ of the Jacobian of the
`torus_map` multiplied by `f (torus_map c R θ)`.

We also define a predicate saying that `f ∘ torus_map c R` is integrable on the cube
`[0, (λ _, 2\pi)]`.

## Main definitions

* `torus_map c R`: the generalized multidimensional exponential map from `ℝⁿ` to `ℂⁿ` that sends
  $θ=(θ_0,…,θ_{n-1})$ to $z=(z_0,…,z_{n-1})$, where $z_k= c_k + R_ke^{θ_k i}$;

* `torus_integrable f c R`: a function `f : ℂⁿ → E` is integrable over the generalized torus
  with center `c : ℂⁿ` and radius `R : ℝⁿ` if `f ∘ torus_map c R` is integrable on the
  closed cube `Icc (0 : ℝⁿ) (λ _, 2 * π)`;

* `torus_integral f c R`: the integral of a function `f : ℂⁿ → E` over a torus with
  center `c ∈ ℂⁿ` and radius `R ∈ ℝⁿ` defined as
  $\iiint_{[0, 2 * π]} (∏_{k = 1}^{n} i R_k e^{θ_k * i}) • f (c + Re^{θ_k i})\,dθ_0…dθ_{k-1}$.

## Main statements

* `torus_integral_dim0`, `torus_integral_dim1`, `torus_integral_succ`: formulas for `torus_integral`
  in cases of dimension `0`, `1`, and `n + 1`.

## Notations

- `ℝ⁰`, `ℝ¹`, `ℝⁿ`, `ℝⁿ⁺¹`: local notation for `fin 0 → ℝ`, `fin 1 → ℝ`, `fin n → ℝ`, and
  `fin (n + 1) → ℝ`, respectively;
- `ℂ⁰`, `ℂ¹`, `ℂⁿ`, `ℂⁿ⁺¹`: local notation for `fin 0 → ℂ`, `fin 1 → ℂ`, `fin n → ℂ`, and
  `fin (n + 1) → ℂ`, respectively;
- `∯ z in T(c, R), f z`: notation for `torus_integral f c R`;
- `∮ z in C(c, R), f z`: notation for `circle_integral f c R`, defined elsewhere;
- `∏ k, f k`: notation for `finset.prod`, defined elsewhere;
- `π`: notation for `real.pi`, defined elsewhere.

## Tags

integral, torus
-/

variable {n : ℕ}
variables {E : Type*} [normed_add_comm_group E]

noncomputable theory

open complex set measure_theory function filter topological_space
open_locale real big_operators

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

/-!
### `torus_map`, a generalization of a torus
-/

/-- The n dimensional exponential map $θ_i ↦ c + R e^{θ_i*I}, θ ∈ ℝⁿ$ representing
a torus in `ℂⁿ` with center `c ∈ ℂⁿ` and generalized radius `R ∈ ℝⁿ`, so we can adjust
it to every n axis. -/
def torus_map (c : ℂⁿ) (R : ℝⁿ) : ℝⁿ → ℂⁿ :=
λ θ i, c i + R i * exp(θ i * I)

lemma torus_map_sub_center (c : ℂⁿ) (R : ℝⁿ) (θ : ℝⁿ) :
  torus_map c R θ - c = torus_map 0 R θ :=
by { ext1 i, simp [torus_map] }

lemma torus_map_eq_center_iff {c : ℂⁿ} {R : ℝⁿ} {θ : ℝⁿ} :
  torus_map c R θ = c ↔ R = 0 :=
by simp [funext_iff, torus_map, exp_ne_zero]

@[simp] lemma torus_map_zero_radius (c : ℂⁿ) : torus_map c 0 = const ℝⁿ c :=
by { ext1, rw torus_map_eq_center_iff.2 rfl }

/-!
### Integrability of a function on a generalized torus
-/

/-- A function `f : ℂⁿ → E` is integrable on the generalized torus if the function
`f ∘ torus_map c R θ` is integrable on `Icc (0 : ℝⁿ) (λ _, 2 * π)`-/
def torus_integrable (f : ℂⁿ → E) (c : ℂⁿ) (R : ℝⁿ) : Prop :=
  integrable_on (λ (θ : ℝⁿ), f (torus_map c R θ)) (Icc (0 : ℝⁿ) (λ _, 2 * π)) volume

namespace torus_integrable

variables {f g : ℂⁿ → E} {c : ℂⁿ} {R : ℝⁿ}

/-- Constant functions are torus integrable -/
lemma torus_integrable_const (a : E) (c : ℂⁿ) (R : ℝⁿ) :
  torus_integrable (λ _, a) c R :=
by simp [torus_integrable, measure_Icc_lt_top]

/-- If `f` is torus integrable then `-f` is torus integrable. -/
protected lemma neg (hf : torus_integrable f c R) : torus_integrable (-f) c R := hf.neg

/-- If `f` and `g` are two torus integrable functions, then so is `f + g`. -/
protected lemma add (hf : torus_integrable f c R) (hg : torus_integrable g c R) :
  torus_integrable (f + g) c R :=
hf.add hg

/-- If `f` and `g` are two torus integrable functions, then so is `f - g`. -/
protected lemma sub (hf : torus_integrable f c R) (hg : torus_integrable g c R) :
  torus_integrable (f - g) c R :=
hf.sub hg

lemma torus_integrable_zero_radius {f : ℂⁿ → E} {c : ℂⁿ} :
  torus_integrable f c 0 :=
begin
  rw [torus_integrable, torus_map_zero_radius],
  apply torus_integrable_const (f c) c 0,
end

/--The function given in the definition of `torus_integral` is integrable. -/
lemma function_integrable [normed_space ℂ E] (hf : torus_integrable f c R) :
  integrable_on (λ (θ : ℝⁿ), (∏ i, R i * exp(θ i * I) * I : ℂ) • f (torus_map c R θ))
                (Icc (0 : ℝⁿ) (λ _, 2 * π)) volume :=
begin
  refine (hf.norm.const_mul (∏ i, |R i|)).mono' _ _,
  { refine (continuous.ae_strongly_measurable _).smul hf.1,
    exact continuous_finset_prod finset.univ (λ i hi, (continuous_const.mul
      (((continuous_of_real.comp (continuous_apply i)).mul continuous_const).cexp)).mul
      continuous_const) },
  simp [norm_smul, map_prod],
end

end torus_integrable

variables [normed_space ℂ E] [complete_space E] {f g : ℂⁿ → E} {c : ℂⁿ} {R : ℝⁿ}

/--The definition of the integral over a generalized torus with center `c ∈ ℂⁿ` and radius `R ∈ ℝⁿ`
as the `•`-product of the derivative of `torus_map` and `f (torus_map c R θ)`-/
def torus_integral (f : ℂⁿ → E) (c : ℂⁿ) (R : ℝⁿ) :=
∫ (θ : ℝⁿ) in Icc (0 : ℝⁿ) (λ _, 2 * π), (∏ i, R i * exp(θ i * I) * I : ℂ) • f (torus_map c R θ)

notation `∯` binders ` in ` `T(` c `, ` R `)` `, ` r:(scoped:60 f, torus_integral f c R) := r

lemma torus_integral_radius_zero (hn : n ≠ 0) (f : ℂⁿ → E) (c : ℂⁿ): ∯ x in T(c, 0), f x = 0 :=
by simp only [torus_integral, pi.zero_apply, of_real_zero, mul_zero, zero_mul, fin.prod_const,
  zero_pow' n hn, zero_smul, integral_zero]

lemma torus_integral_neg (f : ℂⁿ → E) (c : ℂⁿ) (R : ℝⁿ) :
  ∯ x in T(c, R), -f x = -∯ x in T(c, R), f x :=
by simp [torus_integral, integral_neg]

lemma torus_integral_add (hf : torus_integrable f c R) (hg : torus_integrable g c R) :
  ∯ x in T(c, R), f x + g x = (∯ x in T(c, R), f x) + ∯ x in T(c, R), g x :=
by simpa only [torus_integral, smul_add, pi.add_apply]
  using integral_add hf.function_integrable hg.function_integrable

lemma torus_integral_sub (hf : torus_integrable f c R) (hg : torus_integrable g c R) :
  ∯ x in T(c, R), f x - g x = (∯ x in T(c, R), f x) - ∯ x in T(c, R), g x :=
by simpa only [sub_eq_add_neg, ← torus_integral_neg] using torus_integral_add hf hg.neg

lemma torus_integral_smul {𝕜 : Type*} [is_R_or_C 𝕜] [normed_space 𝕜 E] [smul_comm_class 𝕜 ℂ E]
  (a : 𝕜) (f : ℂⁿ → E) (c : ℂⁿ) (R : ℝⁿ) :
  ∯ x in T(c, R), a • f x = a • ∯ x in T(c, R), f x :=
by simp only [torus_integral, integral_smul, ← smul_comm a]

lemma torus_integral_const_mul (a : ℂ) (f : ℂⁿ → ℂ) (c : ℂⁿ) (R : ℝⁿ) :
  ∯ x in T(c, R), a * f x = a * ∯ x in T(c, R), f x :=
torus_integral_smul a f c R

/--If for all `θ : ℝⁿ`, `‖f (torus_map c R θ)‖` is less than or equal to a constant `C : ℝ`, then
`‖∯ x in T(c, R), f x‖` is less than or equal to `(2 * π)^n * (∏ i, |R i|) * C`-/
lemma norm_torus_integral_le_of_norm_le_const {C : ℝ} (hf : ∀ θ, ‖f (torus_map c R θ)‖ ≤ C) :
  ‖∯ x in T(c, R), f x‖ ≤ (2 * π)^(n: ℕ) * (∏ i, |R i|) * C :=
calc ‖∯ x in T(c, R), f x‖ ≤ (∏ i, |R i|) * C * (volume (Icc (0 : ℝⁿ) (λ _, 2 * π))).to_real :
  norm_set_integral_le_of_norm_le_const' measure_Icc_lt_top measurable_set_Icc $ λ θ hθ,
    ( calc ‖(∏ i : fin n, R i * exp (θ i * I) * I : ℂ) • f (torus_map c R θ)‖
          = (∏ i : fin n, |R i|) * ‖f (torus_map c R θ)‖ : by simp [norm_smul]
      ... ≤ (∏ i : fin n, |R i|) * C :
        mul_le_mul_of_nonneg_left (hf _) (finset.prod_nonneg $ λ _ _, abs_nonneg _) )
... = (2 * π)^(n: ℕ) * (∏ i, |R i|) * C :
  by simp only [pi.zero_def, real.volume_Icc_pi_to_real (λ _, real.two_pi_pos.le), sub_zero,
      fin.prod_const, mul_assoc, mul_comm ((2 * π) ^ (n : ℕ))]

@[simp] lemma torus_integral_dim0 (f : ℂ⁰ → E) (c : ℂ⁰) (R : ℝ⁰) : ∯ x in T(c, R), f x = f c :=
by simp only [torus_integral, fin.prod_univ_zero, one_smul,
  subsingleton.elim (λ i : fin 0, 2 * π) 0, Icc_self, measure.restrict_singleton, volume_pi,
  integral_smul_measure, integral_dirac, measure.pi_of_empty _ 0,
  measure.dirac_apply_of_mem (mem_singleton _), subsingleton.elim (torus_map c R 0) c]

/-- In dimension one, `torus_integral` is the same as `circle_integral`
(up to the natural equivalence between `ℂ` and `fin 1 → ℂ`). -/
lemma torus_integral_dim1 (f : ℂ¹ → E) (c : ℂ¹) (R : ℝ¹) :
  ∯ x in T(c, R), f x = ∮ z in C(c 0, R 0), f (λ _, z) :=
begin
  have : (λ (x : ℝ) (b : fin 1), x) ⁻¹' Icc 0 (λ _, 2 * π) = Icc 0 (2 * π),
    from (order_iso.fun_unique (fin 1) ℝ).symm.preimage_Icc _ _,
  simp only [torus_integral, circle_integral, interval_integral.integral_of_le real.two_pi_pos.le,
    measure.restrict_congr_set Ioc_ae_eq_Icc, deriv_circle_map, fin.prod_univ_one,
    ← ((volume_preserving_fun_unique (fin 1) ℝ).symm _).set_integral_preimage_emb
      (measurable_equiv.measurable_embedding _), this, measurable_equiv.fun_unique_symm_apply],
  simp only [torus_map, circle_map, zero_add],
  rcongr
end

/-- Recurrent formula for `torus_integral`, see also `torus_integral_succ`. -/
lemma torus_integral_succ_above {f : ℂⁿ⁺¹ → E} {c : ℂⁿ⁺¹} {R : ℝⁿ⁺¹} (hf : torus_integrable f c R)
  (i : fin (n + 1)) :
  ∯ x in T(c, R), f x =
    ∮ x in C(c i, R i), ∯ y in T(c ∘ i.succ_above, R ∘ i.succ_above), f (i.insert_nth x y) :=
begin
  set e : ℝ × ℝⁿ ≃ᵐ ℝⁿ⁺¹ := (measurable_equiv.pi_fin_succ_above_equiv (λ _, ℝ) i).symm,
  have hem : measure_preserving e,
    from (volume_preserving_pi_fin_succ_above_equiv (λ j : fin (n + 1), ℝ) i).symm _,
  have heπ : e ⁻¹' (Icc 0 (λ _, 2 * π)) = Icc 0 (2 * π) ×ˢ Icc (0 : ℝⁿ) (λ _, 2 * π),
    from ((order_iso.pi_fin_succ_above_iso (λ _, ℝ) i).symm.preimage_Icc _ _).trans
      (Icc_prod_eq _ _),
  rw [torus_integral, ← hem.map_eq, set_integral_map_equiv, heπ, measure.volume_eq_prod,
    set_integral_prod, circle_integral_def_Icc],
  { refine set_integral_congr measurable_set_Icc (λ θ hθ, _),
    simp only [torus_integral, ← integral_smul, deriv_circle_map, i.prod_univ_succ_above _,
      smul_smul, torus_map, circle_map_zero],
    refine set_integral_congr measurable_set_Icc (λ Θ hΘ, _),
    simp only [measurable_equiv.pi_fin_succ_above_equiv_symm_apply, i.insert_nth_apply_same,
      i.insert_nth_apply_succ_above, (∘)],
    congr' 2,
    simp only [funext_iff, i.forall_iff_succ_above, circle_map, fin.insert_nth_apply_same,
      eq_self_iff_true, fin.insert_nth_apply_succ_above, implies_true_iff, and_self] },
  { have := hf.function_integrable,
    rwa [← hem.integrable_on_comp_preimage e.measurable_embedding, heπ] at this }
end

/-- Recurrent formula for `torus_integral`, see also `torus_integral_succ_above`. -/
lemma torus_integral_succ {f : ℂⁿ⁺¹ → E} {c : ℂⁿ⁺¹} {R : ℝⁿ⁺¹} (hf : torus_integrable f c R) :
  ∯ x in T(c, R), f x =
    ∮ x in C(c 0, R 0), ∯ y in T(c ∘ fin.succ, R ∘ fin.succ), f (fin.cons x y) :=
by simpa using torus_integral_succ_above hf 0


variables (α : Type*) (p q : α → Prop)

theorem thml : (∀ x, p x ∧ q x) → (∀ x, p x) :=
assume h,
assume y,
show p y, from and.elim_left(h y)

theorem thmr : (∀ x, p x ∧ q x) →  (∀ x, q x) :=
assume h,
assume y,
show q y, from and.elim_right(h y)

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
iff.intro
  (assume h : (∀ x, p x ∧ q x),
  and.intro (assume t : α, and.left (h t)) (assume t : α, and.right (h t)))
  (assume h, assume t, and.intro ((and.left h) t) ((and.right h) t))

example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
assume h : (∀ x, p x → q x),
assume g : (∀ x, p x),
assume y : α,
(h y) (g y)

example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
assume h,
or.elim h
  (assume g, assume t, or.inl (g t)) (assume g, assume t, or.inr (g t))


import order.boolean_algebra

open lattice

/-
EXERCISE:

In mathematics, a Boolean ring R is a ring for which x² = x for all x in R.

Define a class of boolean rings. It should extend the typeclass of rings.

-/

class boolean_ring (α : Type*) := sorry

open boolean_ring

universe u

variables {α : Type u} [boolean_ring α]

/-
Since the join operation ∨ in a Boolean algebra is often written additively, it makes sense in this context to denote ring addition by ⊕, a symbol that is often used to denote exclusive or.

Given a Boolean ring R, for x and y in R we can define

    x ∧ y = xy,

    x ∨ y = x ⊕ y ⊕ xy,

    ¬x = 1 ⊕ x.

These operations then satisfy all of the axioms for meets, joins, and complements in a Boolean algebra. Thus every Boolean ring becomes a Boolean algebra.
-/
local notation x ` ⊕ ` y := (x : α) + (y : α)

/- Every Boolean ring R satisfies x ⊕ x = 0 for all x in R, because we know

    x ⊕ x = (x ⊕ x)² = x² ⊕ x² ⊕ x² ⊕ x² = x ⊕ x ⊕ x ⊕ x

and since (R,⊕) is an abelian group, we can subtract x ⊕ x from both sides of this equation, which gives x ⊕ x = 0. A similar proof shows that every Boolean ring is commutative:

    x ⊕ y = (x ⊕ y)² = x² ⊕ xy ⊕ yx ⊕ y² = x ⊕ xy ⊕ yx ⊕ y

 and this yields xy ⊕ yx = 0, which means xy = yx (using the first property above). -/

/- EXERCISE: Prove a lemma which says that ∀ x : α, x ⊕ x = 0 and a lemma that multiplication commutes.-/

/- EXERCISE: Given the boolean_ring instance on α, construct the boolean_algebra instance on α. -/

/- the inf operation should be x * y, the sup operation should be x ⊕ y ⊕ x * y, and the complement operation should be 1 ⊕ x. -/

instance boolean_algebra_of_boolean_ring {α : Type*} [boolean_ring α] : boolean_algebra α :=
{!_!} -- use the hole command for creating a structure stub


/-
Copyright (c) 2023 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic
import ring_theory.principal_ideal_domain -- theory of PIDs

/-

# Principal Ideal Domains

First let's showcase what mathlib has.

Let `R` be a commutative ring.
-/

variables (R : Type) [comm_ring R]

-- We say `R` is a *principal ideal ring* if all ideals are principal.
-- We say `R` is a *domain* if it's an integral domain.
-- We say `R` is a *principal ideal domain* if it's both.
-- So here's how to say "Assume `R` is a PID":

variables [is_principal_ideal_ring R] [is_domain R]

-- Note that both of these are typeclasses, so various things should
-- be automatic.

example : ∀ a b : R, a * b = 0 → a = 0 ∨ b = 0 :=
begin
  intros a b,
  apply eq_zero_or_eq_zero_of_mul_eq_zero, -- typeclass inference
  -- magically extracts the assumption from `is_domain`
end

example : (0 : R) ≠ 1 :=
begin
  -- this is another consequence of being an integral domain
  apply zero_ne_one,
end

example (I : ideal R) : I.is_principal :=
begin
  -- typeclass inference system finds `is_principal_ideal_ring` and
  -- uses it automatically
  exact is_principal_ideal_ring.principal I,
end

example (I : ideal R) : ∃ j, I = ideal.span {j} :=
begin
  -- to make a term of type `is_principal I` you need to give one proof,
  -- but we still need to do `cases` or equivalent (I used `obtain` below)
  -- to get this proof out.
  obtain ⟨h⟩ := is_principal_ideal_ring.principal I,
  exact h,
end

-- product of two PIDs isn't a PID, but only becuase it's not a domain
example (A B : Type) [comm_ring A] [comm_ring B]
  [is_principal_ideal_ring A] [is_principal_ideal_ring B] :
  is_principal_ideal_ring (A × B) :=
{ principal := begin
    intro I,
    obtain ⟨a, (hA : _ = ideal.span _)⟩ := is_principal_ideal_ring.principal (I.map (ring_hom.fst A B)),
    obtain ⟨b, (hB : _ = ideal.span _)⟩ := is_principal_ideal_ring.principal (I.map (ring_hom.snd A B)),
    use (a,b),
    ext,
    simp only [ideal.submodule_span_eq],
    rw ideal.mem_span_singleton,
    split,
    { intro h,
      have h1 : ring_hom.fst A B x ∈ I.map (ring_hom.fst A B),
      { apply ideal.mem_map_of_mem _ h, },
      rw [hA, ideal.mem_span_singleton] at h1,
      rcases h1 with ⟨r, hr⟩,
      have h2 : ring_hom.snd A B x ∈ I.map (ring_hom.snd A B),
      { apply ideal.mem_map_of_mem _ h, },
      rw [hB, ideal.mem_span_singleton] at h2,
      rcases h2 with ⟨s, hs⟩,
      use (r,s),
      change x = (a*r,b*s),
      rw [← hr, ← hs],
      simp only [ring_hom.coe_fst, ring_hom.coe_snd, prod.mk.eta],
    },
    { rintro ⟨⟨r, s⟩, rfl⟩,
      have ha : a ∈ I.map (ring_hom.fst A B),
      { rw [hA, ideal.mem_span_singleton], },
      have hb : b ∈ I.map (ring_hom.snd A B),
      { rw [hB, ideal.mem_span_singleton], },
      rw ideal.mem_map_iff_of_surjective at ha hb,
      { rcases ha with ⟨⟨a, b'⟩, haI, rfl⟩,
        rcases hb with ⟨⟨a', b⟩, hbI, rfl⟩,
        suffices : (a,b) ∈ I,
        { exact ideal.mul_mem_right _ _ this, },
        convert I.add_mem (I.mul_mem_left (1,0) haI)
          (I.mul_mem_left (0,1) hbI);
        simp, },
      { intro a, use (a,0), refl, },
      { intro b, use (0,b), refl, },
    }
  end }