/-
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, Sébastien Gouëzel,
  Rémy Degenne
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.analysis.special_functions.trigonometric
import Mathlib.analysis.calculus.extend_deriv
import Mathlib.PostPort

universes u_1

namespace Mathlib

/-!
# Power function on `ℂ`, `ℝ`, `ℝ≥0`, and `ennreal`

We construct the power functions `x ^ y` where
* `x` and `y` are complex numbers,
* or `x` and `y` are real numbers,
* or `x` is a nonnegative real number and `y` is a real number;
* or `x` is a number from `[0, +∞]` (a.k.a. `ennreal`) and `y` is a real number.

We also prove basic properties of these functions.
-/

namespace complex

/-- The complex power function `x^y`, given by `x^y = exp(y log x)` (where `log` is the principal
determination of the logarithm), unless `x = 0` where one sets `0^0 = 1` and `0^y = 0` for
`y ≠ 0`. -/
def cpow (x : ℂ) (y : ℂ) : ℂ :=
  ite (x = 0) (ite (y = 0) 1 0) (exp (log x * y))

protected instance has_pow : has_pow ℂ ℂ :=
  has_pow.mk cpow

@[simp] theorem cpow_eq_pow (x : ℂ) (y : ℂ) : cpow x y = x ^ y :=
  rfl

theorem cpow_def (x : ℂ) (y : ℂ) : x ^ y = ite (x = 0) (ite (y = 0) 1 0) (exp (log x * y)) :=
  rfl

@[simp] theorem cpow_zero (x : ℂ) : x ^ 0 = 1 := sorry

@[simp] theorem cpow_eq_zero_iff (x : ℂ) (y : ℂ) : x ^ y = 0 ↔ x = 0 ∧ y ≠ 0 := sorry

@[simp] theorem zero_cpow {x : ℂ} (h : x ≠ 0) : 0 ^ x = 0 := sorry

@[simp] theorem cpow_one (x : ℂ) : x ^ 1 = x := sorry

@[simp] theorem one_cpow (x : ℂ) : 1 ^ x = 1 := sorry

theorem cpow_add {x : ℂ} (y : ℂ) (z : ℂ) (hx : x ≠ 0) : x ^ (y + z) = x ^ y * x ^ z := sorry

theorem cpow_mul {x : ℂ} {y : ℂ} (z : ℂ) (h₁ : -real.pi < im (log x * y)) (h₂ : im (log x * y) ≤ real.pi) : x ^ (y * z) = (x ^ y) ^ z := sorry

theorem cpow_neg (x : ℂ) (y : ℂ) : x ^ (-y) = (x ^ y⁻¹) := sorry

theorem cpow_neg_one (x : ℂ) : x ^ (-1) = (x⁻¹) := sorry

@[simp] theorem cpow_nat_cast (x : ℂ) (n : ℕ) : x ^ ↑n = x ^ n := sorry

@[simp] theorem cpow_int_cast (x : ℂ) (n : ℤ) : x ^ ↑n = x ^ n := sorry

theorem cpow_nat_inv_pow (x : ℂ) {n : ℕ} (hn : 0 < n) : (x ^ (↑n⁻¹)) ^ n = x := sorry

end complex

namespace real

/-- The real power function `x^y`, defined as the real part of the complex power function.
For `x > 0`, it is equal to `exp(y log x)`. For `x = 0`, one sets `0^0=1` and `0^y=0` for `y ≠ 0`.
For `x < 0`, the definition is somewhat arbitary as it depends on the choice of a complex
determination of the logarithm. With our conventions, it is equal to `exp (y log x) cos (πy)`. -/
def rpow (x : ℝ) (y : ℝ) : ℝ :=
  complex.re (↑x ^ ↑y)

protected instance has_pow : has_pow ℝ ℝ :=
  has_pow.mk rpow

@[simp] theorem rpow_eq_pow (x : ℝ) (y : ℝ) : rpow x y = x ^ y :=
  rfl

theorem rpow_def (x : ℝ) (y : ℝ) : x ^ y = complex.re (↑x ^ ↑y) :=
  rfl

theorem rpow_def_of_nonneg {x : ℝ} (hx : 0 ≤ x) (y : ℝ) : x ^ y = ite (x = 0) (ite (y = 0) 1 0) (exp (log x * y)) := sorry

theorem rpow_def_of_pos {x : ℝ} (hx : 0 < x) (y : ℝ) : x ^ y = exp (log x * y) := sorry

theorem exp_mul (x : ℝ) (y : ℝ) : exp (x * y) = exp x ^ y :=
  eq.mpr (id (Eq._oldrec (Eq.refl (exp (x * y) = exp x ^ y)) (rpow_def_of_pos (exp_pos x) y)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (exp (x * y) = exp (log (exp x) * y))) (log_exp x))) (Eq.refl (exp (x * y))))

theorem rpow_eq_zero_iff_of_nonneg {x : ℝ} {y : ℝ} (hx : 0 ≤ x) : x ^ y = 0 ↔ x = 0 ∧ y ≠ 0 := sorry

theorem rpow_def_of_neg {x : ℝ} (hx : x < 0) (y : ℝ) : x ^ y = exp (log x * y) * cos (y * pi) := sorry

theorem rpow_def_of_nonpos {x : ℝ} (hx : x ≤ 0) (y : ℝ) : x ^ y = ite (x = 0) (ite (y = 0) 1 0) (exp (log x * y) * cos (y * pi)) := sorry

theorem rpow_pos_of_pos {x : ℝ} (hx : 0 < x) (y : ℝ) : 0 < x ^ y :=
  eq.mpr (id (Eq._oldrec (Eq.refl (0 < x ^ y)) (rpow_def_of_pos hx y))) (exp_pos (log x * y))

@[simp] theorem rpow_zero (x : ℝ) : x ^ 0 = 1 := sorry

@[simp] theorem zero_rpow {x : ℝ} (h : x ≠ 0) : 0 ^ x = 0 := sorry

@[simp] theorem rpow_one (x : ℝ) : x ^ 1 = x := sorry

@[simp] theorem one_rpow (x : ℝ) : 1 ^ x = 1 := sorry

theorem zero_rpow_le_one (x : ℝ) : 0 ^ x ≤ 1 := sorry

theorem zero_rpow_nonneg (x : ℝ) : 0 ≤ 0 ^ x := sorry

theorem rpow_nonneg_of_nonneg {x : ℝ} (hx : 0 ≤ x) (y : ℝ) : 0 ≤ x ^ y := sorry

theorem abs_rpow_le_abs_rpow (x : ℝ) (y : ℝ) : abs (x ^ y) ≤ abs x ^ y := sorry

end real

namespace complex

theorem of_real_cpow {x : ℝ} (hx : 0 ≤ x) (y : ℝ) : ↑(x ^ y) = ↑x ^ ↑y := sorry

@[simp] theorem abs_cpow_real (x : ℂ) (y : ℝ) : abs (x ^ ↑y) = abs x ^ y := sorry

@[simp] theorem abs_cpow_inv_nat (x : ℂ) (n : ℕ) : abs (x ^ (↑n⁻¹)) = abs x ^ (↑n⁻¹) := sorry

end complex

namespace real

theorem rpow_add {x : ℝ} (hx : 0 < x) (y : ℝ) (z : ℝ) : x ^ (y + z) = x ^ y * x ^ z := sorry

theorem rpow_add' {x : ℝ} (hx : 0 ≤ x) {y : ℝ} {z : ℝ} (h : y + z ≠ 0) : x ^ (y + z) = x ^ y * x ^ z := sorry

/-- For `0 ≤ x`, the only problematic case in the equality `x ^ y * x ^ z = x ^ (y + z)` is for
`x = 0` and `y + z = 0`, where the right hand side is `1` while the left hand side can vanish.
The inequality is always true, though, and given in this lemma. -/
theorem le_rpow_add {x : ℝ} (hx : 0 ≤ x) (y : ℝ) (z : ℝ) : x ^ y * x ^ z ≤ x ^ (y + z) := sorry

theorem rpow_mul {x : ℝ} (hx : 0 ≤ x) (y : ℝ) (z : ℝ) : x ^ (y * z) = (x ^ y) ^ z := sorry

theorem rpow_neg {x : ℝ} (hx : 0 ≤ x) (y : ℝ) : x ^ (-y) = (x ^ y⁻¹) := sorry

theorem rpow_sub {x : ℝ} (hx : 0 < x) (y : ℝ) (z : ℝ) : x ^ (y - z) = x ^ y / x ^ z := sorry

theorem rpow_sub' {x : ℝ} (hx : 0 ≤ x) {y : ℝ} {z : ℝ} (h : y - z ≠ 0) : x ^ (y - z) = x ^ y / x ^ z := sorry

@[simp] theorem rpow_nat_cast (x : ℝ) (n : ℕ) : x ^ ↑n = x ^ n := sorry

@[simp] theorem rpow_int_cast (x : ℝ) (n : ℤ) : x ^ ↑n = x ^ n := sorry

theorem rpow_neg_one (x : ℝ) : x ^ (-1) = (x⁻¹) := sorry

theorem mul_rpow {x : ℝ} {y : ℝ} {z : ℝ} (h : 0 ≤ x) (h₁ : 0 ≤ y) : (x * y) ^ z = x ^ z * y ^ z := sorry

theorem inv_rpow {x : ℝ} (hx : 0 ≤ x) (y : ℝ) : x⁻¹ ^ y = (x ^ y⁻¹) := sorry

theorem div_rpow {x : ℝ} {y : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) (z : ℝ) : (x / y) ^ z = x ^ z / y ^ z := sorry

theorem log_rpow {x : ℝ} (hx : 0 < x) (y : ℝ) : log (x ^ y) = y * log x := sorry

theorem rpow_lt_rpow {x : ℝ} {y : ℝ} {z : ℝ} (hx : 0 ≤ x) (hxy : x < y) (hz : 0 < z) : x ^ z < y ^ z := sorry

theorem rpow_le_rpow {x : ℝ} {y : ℝ} {z : ℝ} (h : 0 ≤ x) (h₁ : x ≤ y) (h₂ : 0 ≤ z) : x ^ z ≤ y ^ z := sorry

theorem rpow_lt_rpow_iff {x : ℝ} {y : ℝ} {z : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) (hz : 0 < z) : x ^ z < y ^ z ↔ x < y :=
  { mp := lt_imp_lt_of_le_imp_le fun (h : y ≤ x) => rpow_le_rpow hy h (le_of_lt hz),
    mpr := fun (h : x < y) => rpow_lt_rpow hx h hz }

theorem rpow_le_rpow_iff {x : ℝ} {y : ℝ} {z : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) (hz : 0 < z) : x ^ z ≤ y ^ z ↔ x ≤ y :=
  iff.mpr le_iff_le_iff_lt_iff_lt (rpow_lt_rpow_iff hy hx hz)

theorem rpow_lt_rpow_of_exponent_lt {x : ℝ} {y : ℝ} {z : ℝ} (hx : 1 < x) (hyz : y < z) : x ^ y < x ^ z := sorry

theorem rpow_le_rpow_of_exponent_le {x : ℝ} {y : ℝ} {z : ℝ} (hx : 1 ≤ x) (hyz : y ≤ z) : x ^ y ≤ x ^ z := sorry

theorem rpow_lt_rpow_of_exponent_gt {x : ℝ} {y : ℝ} {z : ℝ} (hx0 : 0 < x) (hx1 : x < 1) (hyz : z < y) : x ^ y < x ^ z := sorry

theorem rpow_le_rpow_of_exponent_ge {x : ℝ} {y : ℝ} {z : ℝ} (hx0 : 0 < x) (hx1 : x ≤ 1) (hyz : z ≤ y) : x ^ y ≤ x ^ z := sorry

theorem rpow_lt_one {x : ℝ} {z : ℝ} (hx1 : 0 ≤ x) (hx2 : x < 1) (hz : 0 < z) : x ^ z < 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (x ^ z < 1)) (Eq.symm (one_rpow z)))) (rpow_lt_rpow hx1 hx2 hz)

theorem rpow_le_one {x : ℝ} {z : ℝ} (hx1 : 0 ≤ x) (hx2 : x ≤ 1) (hz : 0 ≤ z) : x ^ z ≤ 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (x ^ z ≤ 1)) (Eq.symm (one_rpow z)))) (rpow_le_rpow hx1 hx2 hz)

theorem rpow_lt_one_of_one_lt_of_neg {x : ℝ} {z : ℝ} (hx : 1 < x) (hz : z < 0) : x ^ z < 1 := sorry

theorem rpow_le_one_of_one_le_of_nonpos {x : ℝ} {z : ℝ} (hx : 1 ≤ x) (hz : z ≤ 0) : x ^ z ≤ 1 := sorry

theorem one_lt_rpow {x : ℝ} {z : ℝ} (hx : 1 < x) (hz : 0 < z) : 1 < x ^ z :=
  eq.mpr (id (Eq._oldrec (Eq.refl (1 < x ^ z)) (Eq.symm (one_rpow z)))) (rpow_lt_rpow zero_le_one hx hz)

theorem one_le_rpow {x : ℝ} {z : ℝ} (hx : 1 ≤ x) (hz : 0 ≤ z) : 1 ≤ x ^ z :=
  eq.mpr (id (Eq._oldrec (Eq.refl (1 ≤ x ^ z)) (Eq.symm (one_rpow z)))) (rpow_le_rpow zero_le_one hx hz)

theorem one_lt_rpow_of_pos_of_lt_one_of_neg {x : ℝ} {z : ℝ} (hx1 : 0 < x) (hx2 : x < 1) (hz : z < 0) : 1 < x ^ z := sorry

theorem one_le_rpow_of_pos_of_le_one_of_nonpos {x : ℝ} {z : ℝ} (hx1 : 0 < x) (hx2 : x ≤ 1) (hz : z ≤ 0) : 1 ≤ x ^ z := sorry

theorem rpow_lt_one_iff_of_pos {x : ℝ} {y : ℝ} (hx : 0 < x) : x ^ y < 1 ↔ 1 < x ∧ y < 0 ∨ x < 1 ∧ 0 < y := sorry

theorem rpow_lt_one_iff {x : ℝ} {y : ℝ} (hx : 0 ≤ x) : x ^ y < 1 ↔ x = 0 ∧ y ≠ 0 ∨ 1 < x ∧ y < 0 ∨ x < 1 ∧ 0 < y := sorry

theorem one_lt_rpow_iff_of_pos {x : ℝ} {y : ℝ} (hx : 0 < x) : 1 < x ^ y ↔ 1 < x ∧ 0 < y ∨ x < 1 ∧ y < 0 := sorry

theorem one_lt_rpow_iff {x : ℝ} {y : ℝ} (hx : 0 ≤ x) : 1 < x ^ y ↔ 1 < x ∧ 0 < y ∨ 0 < x ∧ x < 1 ∧ y < 0 := sorry

theorem rpow_le_one_iff_of_pos {x : ℝ} {y : ℝ} (hx : 0 < x) : x ^ y ≤ 1 ↔ 1 ≤ x ∧ y ≤ 0 ∨ x ≤ 1 ∧ 0 ≤ y := sorry

theorem pow_nat_rpow_nat_inv {x : ℝ} (hx : 0 ≤ x) {n : ℕ} (hn : 0 < n) : (x ^ n) ^ (↑n⁻¹) = x := sorry

theorem rpow_nat_inv_pow_nat {x : ℝ} (hx : 0 ≤ x) {n : ℕ} (hn : 0 < n) : (x ^ (↑n⁻¹)) ^ n = x := sorry

theorem continuous_rpow_aux1 : continuous fun (p : Subtype fun (p : ℝ × ℝ) => 0 < prod.fst p) => prod.fst (subtype.val p) ^ prod.snd (subtype.val p) := sorry

theorem continuous_rpow_aux2 : continuous fun (p : Subtype fun (p : ℝ × ℝ) => prod.fst p < 0) => prod.fst (subtype.val p) ^ prod.snd (subtype.val p) := sorry

theorem continuous_at_rpow_of_ne_zero {x : ℝ} (hx : x ≠ 0) (y : ℝ) : continuous_at (fun (p : ℝ × ℝ) => prod.fst p ^ prod.snd p) (x, y) := sorry

theorem continuous_rpow_aux3 : continuous fun (p : Subtype fun (p : ℝ × ℝ) => 0 < prod.snd p) => prod.fst (subtype.val p) ^ prod.snd (subtype.val p) := sorry

theorem continuous_at_rpow_of_pos {y : ℝ} (hy : 0 < y) (x : ℝ) : continuous_at (fun (p : ℝ × ℝ) => prod.fst p ^ prod.snd p) (x, y) := sorry

theorem continuous_at_rpow {x : ℝ} {y : ℝ} (h : x ≠ 0 ∨ 0 < y) : continuous_at (fun (p : ℝ × ℝ) => prod.fst p ^ prod.snd p) (x, y) :=
  or.dcases_on h (fun (h : x ≠ 0) => continuous_at_rpow_of_ne_zero h y) fun (h : 0 < y) => continuous_at_rpow_of_pos h x

/--
`real.rpow` is continuous at all points except for the lower half of the y-axis.
In other words, the function `λp:ℝ×ℝ, p.1^p.2` is continuous at `(x, y)` if `x ≠ 0` or `y > 0`.

Multiple forms of the claim is provided in the current section.
-/
theorem continuous_rpow {α : Type u_1} [topological_space α] {f : α → ℝ} {g : α → ℝ} (h : ∀ (a : α), f a ≠ 0 ∨ 0 < g a) (hf : continuous f) (hg : continuous g) : continuous fun (a : α) => f a ^ g a := sorry

theorem continuous_rpow_of_ne_zero {α : Type u_1} [topological_space α] {f : α → ℝ} {g : α → ℝ} (h : ∀ (a : α), f a ≠ 0) (hf : continuous f) (hg : continuous g) : continuous fun (a : α) => f a ^ g a :=
  continuous_rpow (fun (a : α) => Or.inl (h a)) hf hg

theorem continuous_rpow_of_pos {α : Type u_1} [topological_space α] {f : α → ℝ} {g : α → ℝ} (h : ∀ (a : α), 0 < g a) (hf : continuous f) (hg : continuous g) : continuous fun (a : α) => f a ^ g a :=
  continuous_rpow (fun (a : α) => Or.inr (h a)) hf hg

theorem has_deriv_at_rpow_of_pos {x : ℝ} (h : 0 < x) (p : ℝ) : has_deriv_at (fun (x : ℝ) => x ^ p) (p * x ^ (p - 1)) x := sorry

theorem has_deriv_at_rpow_of_neg {x : ℝ} (h : x < 0) (p : ℝ) : has_deriv_at (fun (x : ℝ) => x ^ p) (p * x ^ (p - 1)) x := sorry

theorem has_deriv_at_rpow {x : ℝ} (h : x ≠ 0) (p : ℝ) : has_deriv_at (fun (x : ℝ) => x ^ p) (p * x ^ (p - 1)) x :=
  or.dcases_on (lt_trichotomy x 0) (fun (H : x < 0) => has_deriv_at_rpow_of_neg H p)
    fun (h_1 : x = 0 ∨ 0 < x) =>
      or.dcases_on h_1 (fun (H : x = 0) => false.elim (h H)) fun (H : 0 < x) => has_deriv_at_rpow_of_pos H p

theorem has_deriv_at_rpow_zero_of_one_le {p : ℝ} (h : 1 ≤ p) : has_deriv_at (fun (x : ℝ) => x ^ p) (p * 0 ^ (p - 1)) 0 := sorry

theorem has_deriv_at_rpow_of_one_le (x : ℝ) {p : ℝ} (h : 1 ≤ p) : has_deriv_at (fun (x : ℝ) => x ^ p) (p * x ^ (p - 1)) x := sorry

theorem sqrt_eq_rpow : sqrt = fun (x : ℝ) => x ^ (1 / bit0 1) := sorry

end real

theorem real.measurable_rpow : measurable fun (p : ℝ × ℝ) => prod.fst p ^ prod.snd p := sorry

theorem measurable.rpow {α : Type u_1} [measurable_space α] {f : α → ℝ} {g : α → ℝ} (hf : measurable f) (hg : measurable g) : measurable fun (a : α) => f a ^ g a :=
  id (measurable.comp real.measurable_rpow (measurable.prod hf hg))

theorem real.measurable_rpow_const {y : ℝ} : measurable fun (x : ℝ) => x ^ y :=
  id (measurable.comp real.measurable_rpow (measurable.prod measurable_id (id measurable_const)))

theorem measurable.rpow_const {α : Type u_1} [measurable_space α] {f : α → ℝ} (hf : measurable f) {y : ℝ} : measurable fun (a : α) => f a ^ y :=
  measurable.rpow hf measurable_const

/- Differentiability statements for the power of a function, when the function does not vanish
and the exponent is arbitrary-/

theorem has_deriv_within_at.rpow {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} {s : set ℝ} (p : ℝ) (hf : has_deriv_within_at f f' s x) (hx : f x ≠ 0) : has_deriv_within_at (fun (y : ℝ) => f y ^ p) (f' * p * f x ^ (p - 1)) s x := sorry

theorem has_deriv_at.rpow {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} (p : ℝ) (hf : has_deriv_at f f' x) (hx : f x ≠ 0) : has_deriv_at (fun (y : ℝ) => f y ^ p) (f' * p * f x ^ (p - 1)) x := sorry

theorem differentiable_within_at.rpow {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} (p : ℝ) (hf : differentiable_within_at ℝ f s x) (hx : f x ≠ 0) : differentiable_within_at ℝ (fun (x : ℝ) => f x ^ p) s x :=
  has_deriv_within_at.differentiable_within_at
    (has_deriv_within_at.rpow p (differentiable_within_at.has_deriv_within_at hf) hx)

@[simp] theorem differentiable_at.rpow {f : ℝ → ℝ} {x : ℝ} (p : ℝ) (hf : differentiable_at ℝ f x) (hx : f x ≠ 0) : differentiable_at ℝ (fun (x : ℝ) => f x ^ p) x :=
  has_deriv_at.differentiable_at (has_deriv_at.rpow p (differentiable_at.has_deriv_at hf) hx)

theorem differentiable_on.rpow {f : ℝ → ℝ} {s : set ℝ} (p : ℝ) (hf : differentiable_on ℝ f s) (hx : ∀ (x : ℝ), x ∈ s → f x ≠ 0) : differentiable_on ℝ (fun (x : ℝ) => f x ^ p) s :=
  fun (x : ℝ) (h : x ∈ s) => differentiable_within_at.rpow p (hf x h) (hx x h)

@[simp] theorem differentiable.rpow {f : ℝ → ℝ} (p : ℝ) (hf : differentiable ℝ f) (hx : ∀ (x : ℝ), f x ≠ 0) : differentiable ℝ fun (x : ℝ) => f x ^ p :=
  fun (x : ℝ) => differentiable_at.rpow p (hf x) (hx x)

theorem deriv_within_rpow {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} (p : ℝ) (hf : differentiable_within_at ℝ f s x) (hx : f x ≠ 0) (hxs : unique_diff_within_at ℝ s x) : deriv_within (fun (x : ℝ) => f x ^ p) s x = deriv_within f s x * p * f x ^ (p - 1) :=
  has_deriv_within_at.deriv_within (has_deriv_within_at.rpow p (differentiable_within_at.has_deriv_within_at hf) hx) hxs

@[simp] theorem deriv_rpow {f : ℝ → ℝ} {x : ℝ} (p : ℝ) (hf : differentiable_at ℝ f x) (hx : f x ≠ 0) : deriv (fun (x : ℝ) => f x ^ p) x = deriv f x * p * f x ^ (p - 1) :=
  has_deriv_at.deriv (has_deriv_at.rpow p (differentiable_at.has_deriv_at hf) hx)

/- Differentiability statements for the power of a function, when the function may vanish
but the exponent is at least one. -/

theorem has_deriv_within_at.rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} {s : set ℝ} {p : ℝ} (hf : has_deriv_within_at f f' s x) (hp : 1 ≤ p) : has_deriv_within_at (fun (y : ℝ) => f y ^ p) (f' * p * f x ^ (p - 1)) s x := sorry

theorem has_deriv_at.rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} {p : ℝ} (hf : has_deriv_at f f' x) (hp : 1 ≤ p) : has_deriv_at (fun (y : ℝ) => f y ^ p) (f' * p * f x ^ (p - 1)) x := sorry

theorem differentiable_within_at.rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} {p : ℝ} (hf : differentiable_within_at ℝ f s x) (hp : 1 ≤ p) : differentiable_within_at ℝ (fun (x : ℝ) => f x ^ p) s x :=
  has_deriv_within_at.differentiable_within_at
    (has_deriv_within_at.rpow_of_one_le (differentiable_within_at.has_deriv_within_at hf) hp)

@[simp] theorem differentiable_at.rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {p : ℝ} (hf : differentiable_at ℝ f x) (hp : 1 ≤ p) : differentiable_at ℝ (fun (x : ℝ) => f x ^ p) x :=
  has_deriv_at.differentiable_at (has_deriv_at.rpow_of_one_le (differentiable_at.has_deriv_at hf) hp)

theorem differentiable_on.rpow_of_one_le {f : ℝ → ℝ} {s : set ℝ} {p : ℝ} (hf : differentiable_on ℝ f s) (hp : 1 ≤ p) : differentiable_on ℝ (fun (x : ℝ) => f x ^ p) s :=
  fun (x : ℝ) (h : x ∈ s) => differentiable_within_at.rpow_of_one_le (hf x h) hp

@[simp] theorem differentiable.rpow_of_one_le {f : ℝ → ℝ} {p : ℝ} (hf : differentiable ℝ f) (hp : 1 ≤ p) : differentiable ℝ fun (x : ℝ) => f x ^ p :=
  fun (x : ℝ) => differentiable_at.rpow_of_one_le (hf x) hp

theorem deriv_within_rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} {p : ℝ} (hf : differentiable_within_at ℝ f s x) (hp : 1 ≤ p) (hxs : unique_diff_within_at ℝ s x) : deriv_within (fun (x : ℝ) => f x ^ p) s x = deriv_within f s x * p * f x ^ (p - 1) :=
  has_deriv_within_at.deriv_within
    (has_deriv_within_at.rpow_of_one_le (differentiable_within_at.has_deriv_within_at hf) hp) hxs

@[simp] theorem deriv_rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {p : ℝ} (hf : differentiable_at ℝ f x) (hp : 1 ≤ p) : deriv (fun (x : ℝ) => f x ^ p) x = deriv f x * p * f x ^ (p - 1) :=
  has_deriv_at.deriv (has_deriv_at.rpow_of_one_le (differentiable_at.has_deriv_at hf) hp)

/- Differentiability statements for the square root of a function, when the function does not
vanish -/

theorem has_deriv_within_at.sqrt {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} {s : set ℝ} (hf : has_deriv_within_at f f' s x) (hx : f x ≠ 0) : has_deriv_within_at (fun (y : ℝ) => real.sqrt (f y)) (f' / (bit0 1 * real.sqrt (f x))) s x := sorry

theorem has_deriv_at.sqrt {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} (hf : has_deriv_at f f' x) (hx : f x ≠ 0) : has_deriv_at (fun (y : ℝ) => real.sqrt (f y)) (f' / (bit0 1 * real.sqrt (f x))) x := sorry

theorem differentiable_within_at.sqrt {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} (hf : differentiable_within_at ℝ f s x) (hx : f x ≠ 0) : differentiable_within_at ℝ (fun (x : ℝ) => real.sqrt (f x)) s x :=
  has_deriv_within_at.differentiable_within_at
    (has_deriv_within_at.sqrt (differentiable_within_at.has_deriv_within_at hf) hx)

@[simp] theorem differentiable_at.sqrt {f : ℝ → ℝ} {x : ℝ} (hf : differentiable_at ℝ f x) (hx : f x ≠ 0) : differentiable_at ℝ (fun (x : ℝ) => real.sqrt (f x)) x :=
  has_deriv_at.differentiable_at (has_deriv_at.sqrt (differentiable_at.has_deriv_at hf) hx)

theorem differentiable_on.sqrt {f : ℝ → ℝ} {s : set ℝ} (hf : differentiable_on ℝ f s) (hx : ∀ (x : ℝ), x ∈ s → f x ≠ 0) : differentiable_on ℝ (fun (x : ℝ) => real.sqrt (f x)) s :=
  fun (x : ℝ) (h : x ∈ s) => differentiable_within_at.sqrt (hf x h) (hx x h)

@[simp] theorem differentiable.sqrt {f : ℝ → ℝ} (hf : differentiable ℝ f) (hx : ∀ (x : ℝ), f x ≠ 0) : differentiable ℝ fun (x : ℝ) => real.sqrt (f x) :=
  fun (x : ℝ) => differentiable_at.sqrt (hf x) (hx x)

theorem deriv_within_sqrt {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} (hf : differentiable_within_at ℝ f s x) (hx : f x ≠ 0) (hxs : unique_diff_within_at ℝ s x) : deriv_within (fun (x : ℝ) => real.sqrt (f x)) s x = deriv_within f s x / (bit0 1 * real.sqrt (f x)) :=
  has_deriv_within_at.deriv_within (has_deriv_within_at.sqrt (differentiable_within_at.has_deriv_within_at hf) hx) hxs

@[simp] theorem deriv_sqrt {f : ℝ → ℝ} {x : ℝ} (hf : differentiable_at ℝ f x) (hx : f x ≠ 0) : deriv (fun (x : ℝ) => real.sqrt (f x)) x = deriv f x / (bit0 1 * real.sqrt (f x)) :=
  has_deriv_at.deriv (has_deriv_at.sqrt (differentiable_at.has_deriv_at hf) hx)

/-- The function `x ^ y` tends to `+∞` at `+∞` for any positive real `y`. -/
theorem tendsto_rpow_at_top {y : ℝ} (hy : 0 < y) : filter.tendsto (fun (x : ℝ) => x ^ y) filter.at_top filter.at_top := sorry

/-- The function `x ^ (-y)` tends to `0` at `+∞` for any positive real `y`. -/
theorem tendsto_rpow_neg_at_top {y : ℝ} (hy : 0 < y) : filter.tendsto (fun (x : ℝ) => x ^ (-y)) filter.at_top (nhds 0) := sorry

/-- The function `x ^ (a / (b * x + c))` tends to `1` at `+∞`, for any real numbers `a`, `b`, and
`c` such that `b` is nonzero. -/
theorem tendsto_rpow_div_mul_add (a : ℝ) (b : ℝ) (c : ℝ) (hb : 0 ≠ b) : filter.tendsto (fun (x : ℝ) => x ^ (a / (b * x + c))) filter.at_top (nhds 1) := sorry

/-- The function `x ^ (1 / x)` tends to `1` at `+∞`. -/
theorem tendsto_rpow_div : filter.tendsto (fun (x : ℝ) => x ^ (1 / x)) filter.at_top (nhds 1) := sorry

/-- The function `x ^ (-1 / x)` tends to `1` at `+∞`. -/
theorem tendsto_rpow_neg_div : filter.tendsto (fun (x : ℝ) => x ^ (-1 / x)) filter.at_top (nhds 1) := sorry

namespace nnreal

/-- The nonnegative real power function `x^y`, defined for `x : ℝ≥0` and `y : ℝ ` as the
restriction of the real power function. For `x > 0`, it is equal to `exp (y log x)`. For `x = 0`,
one sets `0 ^ 0 = 1` and `0 ^ y = 0` for `y ≠ 0`. -/
def rpow (x : nnreal) (y : ℝ) : nnreal :=
  { val := ↑x ^ y, property := sorry }

protected instance real.has_pow : has_pow nnreal ℝ :=
  has_pow.mk rpow

@[simp] theorem rpow_eq_pow (x : nnreal) (y : ℝ) : rpow x y = x ^ y :=
  rfl

@[simp] theorem coe_rpow (x : nnreal) (y : ℝ) : ↑(x ^ y) = ↑x ^ y :=
  rfl

@[simp] theorem rpow_zero (x : nnreal) : x ^ 0 = 1 :=
  nnreal.eq (real.rpow_zero ↑x)

@[simp] theorem rpow_eq_zero_iff {x : nnreal} {y : ℝ} : x ^ y = 0 ↔ x = 0 ∧ y ≠ 0 := sorry

@[simp] theorem zero_rpow {x : ℝ} (h : x ≠ 0) : 0 ^ x = 0 :=
  nnreal.eq (real.zero_rpow h)

@[simp] theorem rpow_one (x : nnreal) : x ^ 1 = x :=
  nnreal.eq (real.rpow_one ↑x)

@[simp] theorem one_rpow (x : ℝ) : 1 ^ x = 1 :=
  nnreal.eq (real.one_rpow x)

theorem rpow_add {x : nnreal} (hx : x ≠ 0) (y : ℝ) (z : ℝ) : x ^ (y + z) = x ^ y * x ^ z :=
  nnreal.eq (real.rpow_add (iff.mpr pos_iff_ne_zero hx) y z)

theorem rpow_add' (x : nnreal) {y : ℝ} {z : ℝ} (h : y + z ≠ 0) : x ^ (y + z) = x ^ y * x ^ z :=
  nnreal.eq (real.rpow_add' (subtype.property x) h)

theorem rpow_mul (x : nnreal) (y : ℝ) (z : ℝ) : x ^ (y * z) = (x ^ y) ^ z :=
  nnreal.eq (real.rpow_mul (subtype.property x) y z)

theorem rpow_neg (x : nnreal) (y : ℝ) : x ^ (-y) = (x ^ y⁻¹) :=
  nnreal.eq (real.rpow_neg (subtype.property x) y)

theorem rpow_neg_one (x : nnreal) : x ^ (-1) = (x⁻¹) := sorry

theorem rpow_sub {x : nnreal} (hx : x ≠ 0) (y : ℝ) (z : ℝ) : x ^ (y - z) = x ^ y / x ^ z :=
  nnreal.eq (real.rpow_sub (iff.mpr pos_iff_ne_zero hx) y z)

theorem rpow_sub' (x : nnreal) {y : ℝ} {z : ℝ} (h : y - z ≠ 0) : x ^ (y - z) = x ^ y / x ^ z :=
  nnreal.eq (real.rpow_sub' (subtype.property x) h)

theorem inv_rpow (x : nnreal) (y : ℝ) : x⁻¹ ^ y = (x ^ y⁻¹) :=
  nnreal.eq (real.inv_rpow (subtype.property x) y)

theorem div_rpow (x : nnreal) (y : nnreal) (z : ℝ) : (x / y) ^ z = x ^ z / y ^ z :=
  nnreal.eq (real.div_rpow (subtype.property x) (subtype.property y) z)

@[simp] theorem rpow_nat_cast (x : nnreal) (n : ℕ) : x ^ ↑n = x ^ n := sorry

theorem mul_rpow {x : nnreal} {y : nnreal} {z : ℝ} : (x * y) ^ z = x ^ z * y ^ z :=
  nnreal.eq (real.mul_rpow (subtype.property x) (subtype.property y))

theorem rpow_le_rpow {x : nnreal} {y : nnreal} {z : ℝ} (h₁ : x ≤ y) (h₂ : 0 ≤ z) : x ^ z ≤ y ^ z :=
  real.rpow_le_rpow (subtype.property x) h₁ h₂

theorem rpow_lt_rpow {x : nnreal} {y : nnreal} {z : ℝ} (h₁ : x < y) (h₂ : 0 < z) : x ^ z < y ^ z :=
  real.rpow_lt_rpow (subtype.property x) h₁ h₂

theorem rpow_lt_rpow_iff {x : nnreal} {y : nnreal} {z : ℝ} (hz : 0 < z) : x ^ z < y ^ z ↔ x < y :=
  real.rpow_lt_rpow_iff (subtype.property x) (subtype.property y) hz

theorem rpow_le_rpow_iff {x : nnreal} {y : nnreal} {z : ℝ} (hz : 0 < z) : x ^ z ≤ y ^ z ↔ x ≤ y :=
  real.rpow_le_rpow_iff (subtype.property x) (subtype.property y) hz

theorem rpow_lt_rpow_of_exponent_lt {x : nnreal} {y : ℝ} {z : ℝ} (hx : 1 < x) (hyz : y < z) : x ^ y < x ^ z :=
  real.rpow_lt_rpow_of_exponent_lt hx hyz

theorem rpow_le_rpow_of_exponent_le {x : nnreal} {y : ℝ} {z : ℝ} (hx : 1 ≤ x) (hyz : y ≤ z) : x ^ y ≤ x ^ z :=
  real.rpow_le_rpow_of_exponent_le hx hyz

theorem rpow_lt_rpow_of_exponent_gt {x : nnreal} {y : ℝ} {z : ℝ} (hx0 : 0 < x) (hx1 : x < 1) (hyz : z < y) : x ^ y < x ^ z :=
  real.rpow_lt_rpow_of_exponent_gt hx0 hx1 hyz

theorem rpow_le_rpow_of_exponent_ge {x : nnreal} {y : ℝ} {z : ℝ} (hx0 : 0 < x) (hx1 : x ≤ 1) (hyz : z ≤ y) : x ^ y ≤ x ^ z :=
  real.rpow_le_rpow_of_exponent_ge hx0 hx1 hyz

theorem rpow_lt_one {x : nnreal} {z : ℝ} (hx : 0 ≤ x) (hx1 : x < 1) (hz : 0 < z) : x ^ z < 1 :=
  real.rpow_lt_one hx hx1 hz

theorem rpow_le_one {x : nnreal} {z : ℝ} (hx2 : x ≤ 1) (hz : 0 ≤ z) : x ^ z ≤ 1 :=
  real.rpow_le_one (subtype.property x) hx2 hz

theorem rpow_lt_one_of_one_lt_of_neg {x : nnreal} {z : ℝ} (hx : 1 < x) (hz : z < 0) : x ^ z < 1 :=
  real.rpow_lt_one_of_one_lt_of_neg hx hz

theorem rpow_le_one_of_one_le_of_nonpos {x : nnreal} {z : ℝ} (hx : 1 ≤ x) (hz : z ≤ 0) : x ^ z ≤ 1 :=
  real.rpow_le_one_of_one_le_of_nonpos hx hz

theorem one_lt_rpow {x : nnreal} {z : ℝ} (hx : 1 < x) (hz : 0 < z) : 1 < x ^ z :=
  real.one_lt_rpow hx hz

theorem one_le_rpow {x : nnreal} {z : ℝ} (h : 1 ≤ x) (h₁ : 0 ≤ z) : 1 ≤ x ^ z :=
  real.one_le_rpow h h₁

theorem one_lt_rpow_of_pos_of_lt_one_of_neg {x : nnreal} {z : ℝ} (hx1 : 0 < x) (hx2 : x < 1) (hz : z < 0) : 1 < x ^ z :=
  real.one_lt_rpow_of_pos_of_lt_one_of_neg hx1 hx2 hz

theorem one_le_rpow_of_pos_of_le_one_of_nonpos {x : nnreal} {z : ℝ} (hx1 : 0 < x) (hx2 : x ≤ 1) (hz : z ≤ 0) : 1 ≤ x ^ z :=
  real.one_le_rpow_of_pos_of_le_one_of_nonpos hx1 hx2 hz

theorem pow_nat_rpow_nat_inv (x : nnreal) {n : ℕ} (hn : 0 < n) : (x ^ n) ^ (↑n⁻¹) = x := sorry

theorem rpow_nat_inv_pow_nat (x : nnreal) {n : ℕ} (hn : 0 < n) : (x ^ (↑n⁻¹)) ^ n = x := sorry

theorem continuous_at_rpow {x : nnreal} {y : ℝ} (h : x ≠ 0 ∨ 0 < y) : continuous_at (fun (p : nnreal × ℝ) => prod.fst p ^ prod.snd p) (x, y) := sorry

theorem of_real_rpow_of_nonneg {x : ℝ} {y : ℝ} (hx : 0 ≤ x) : nnreal.of_real (x ^ y) = nnreal.of_real x ^ y := sorry

end nnreal

theorem nnreal.measurable_rpow : measurable fun (p : nnreal × ℝ) => prod.fst p ^ prod.snd p := sorry

theorem measurable.nnreal_rpow {α : Type u_1} [measurable_space α] {f : α → nnreal} (hf : measurable f) {g : α → ℝ} (hg : measurable g) : measurable fun (a : α) => f a ^ g a :=
  id (measurable.comp nnreal.measurable_rpow (measurable.prod hf hg))

theorem nnreal.measurable_rpow_const {y : ℝ} : measurable fun (a : nnreal) => a ^ y := sorry

theorem measurable.nnreal_rpow_const {α : Type u_1} [measurable_space α] {f : α → nnreal} (hf : measurable f) {y : ℝ} : measurable fun (a : α) => f a ^ y :=
  measurable.nnreal_rpow hf measurable_const

theorem filter.tendsto.nnrpow {α : Type u_1} {f : filter α} {u : α → nnreal} {v : α → ℝ} {x : nnreal} {y : ℝ} (hx : filter.tendsto u f (nhds x)) (hy : filter.tendsto v f (nhds y)) (h : x ≠ 0 ∨ 0 < y) : filter.tendsto (fun (a : α) => u a ^ v a) f (nhds (x ^ y)) :=
  filter.tendsto.comp (nnreal.continuous_at_rpow h) (filter.tendsto.prod_mk_nhds hx hy)

namespace nnreal

theorem continuous_at_rpow_const {x : nnreal} {y : ℝ} (h : x ≠ 0 ∨ 0 ≤ y) : continuous_at (fun (z : nnreal) => z ^ y) x := sorry

theorem continuous_rpow_const {y : ℝ} (h : 0 ≤ y) : continuous fun (x : nnreal) => x ^ y :=
  iff.mpr continuous_iff_continuous_at fun (x : nnreal) => continuous_at_rpow_const (Or.inr h)

end nnreal

namespace ennreal

/-- The real power function `x^y` on extended nonnegative reals, defined for `x : ennreal` and
`y : ℝ` as the restriction of the real power function if `0 < x < ⊤`, and with the natural values
for `0` and `⊤` (i.e., `0 ^ x = 0` for `x > 0`, `1` for `x = 0` and `⊤` for `x < 0`, and
`⊤ ^ x = 1 / 0 ^ x`). -/
def rpow : ennreal → ℝ → ennreal :=
  sorry

protected instance real.has_pow : has_pow ennreal ℝ :=
  has_pow.mk rpow

@[simp] theorem rpow_eq_pow (x : ennreal) (y : ℝ) : rpow x y = x ^ y :=
  rfl

@[simp] theorem rpow_zero {x : ennreal} : x ^ 0 = 1 := sorry

theorem top_rpow_def (y : ℝ) : ⊤ ^ y = ite (0 < y) ⊤ (ite (y = 0) 1 0) :=
  rfl

@[simp] theorem top_rpow_of_pos {y : ℝ} (h : 0 < y) : ⊤ ^ y = ⊤ := sorry

@[simp] theorem top_rpow_of_neg {y : ℝ} (h : y < 0) : ⊤ ^ y = 0 := sorry

@[simp] theorem zero_rpow_of_pos {y : ℝ} (h : 0 < y) : 0 ^ y = 0 := sorry

@[simp] theorem zero_rpow_of_neg {y : ℝ} (h : y < 0) : 0 ^ y = ⊤ := sorry

theorem zero_rpow_def (y : ℝ) : 0 ^ y = ite (0 < y) 0 (ite (y = 0) 1 ⊤) := sorry

theorem coe_rpow_of_ne_zero {x : nnreal} (h : x ≠ 0) (y : ℝ) : ↑x ^ y = ↑(x ^ y) := sorry

theorem coe_rpow_of_nonneg (x : nnreal) {y : ℝ} (h : 0 ≤ y) : ↑x ^ y = ↑(x ^ y) := sorry

theorem coe_rpow_def (x : nnreal) (y : ℝ) : ↑x ^ y = ite (x = 0 ∧ y < 0) ⊤ ↑(x ^ y) :=
  rfl

@[simp] theorem rpow_one (x : ennreal) : x ^ 1 = x := sorry

@[simp] theorem one_rpow (x : ℝ) : 1 ^ x = 1 := sorry

@[simp] theorem rpow_eq_zero_iff {x : ennreal} {y : ℝ} : x ^ y = 0 ↔ x = 0 ∧ 0 < y ∨ x = ⊤ ∧ y < 0 := sorry

@[simp] theorem rpow_eq_top_iff {x : ennreal} {y : ℝ} : x ^ y = ⊤ ↔ x = 0 ∧ y < 0 ∨ x = ⊤ ∧ 0 < y := sorry

theorem rpow_eq_top_iff_of_pos {x : ennreal} {y : ℝ} (hy : 0 < y) : x ^ y = ⊤ ↔ x = ⊤ := sorry

theorem rpow_eq_top_of_nonneg (x : ennreal) {y : ℝ} (hy0 : 0 ≤ y) : x ^ y = ⊤ → x = ⊤ := sorry

theorem rpow_ne_top_of_nonneg {x : ennreal} {y : ℝ} (hy0 : 0 ≤ y) (h : x ≠ ⊤) : x ^ y ≠ ⊤ :=
  mt (rpow_eq_top_of_nonneg x hy0) h

theorem rpow_lt_top_of_nonneg {x : ennreal} {y : ℝ} (hy0 : 0 ≤ y) (h : x ≠ ⊤) : x ^ y < ⊤ :=
  iff.mpr ennreal.lt_top_iff_ne_top (rpow_ne_top_of_nonneg hy0 h)

theorem rpow_add {x : ennreal} (y : ℝ) (z : ℝ) (hx : x ≠ 0) (h'x : x ≠ ⊤) : x ^ (y + z) = x ^ y * x ^ z := sorry

theorem rpow_neg (x : ennreal) (y : ℝ) : x ^ (-y) = (x ^ y⁻¹) := sorry

theorem rpow_neg_one (x : ennreal) : x ^ (-1) = (x⁻¹) := sorry

theorem rpow_mul (x : ennreal) (y : ℝ) (z : ℝ) : x ^ (y * z) = (x ^ y) ^ z := sorry

@[simp] theorem rpow_nat_cast (x : ennreal) (n : ℕ) : x ^ ↑n = x ^ n := sorry

theorem coe_mul_rpow (x : nnreal) (y : nnreal) (z : ℝ) : (↑x * ↑y) ^ z = ↑x ^ z * ↑y ^ z := sorry

theorem mul_rpow_of_ne_top {x : ennreal} {y : ennreal} (hx : x ≠ ⊤) (hy : y ≠ ⊤) (z : ℝ) : (x * y) ^ z = x ^ z * y ^ z := sorry

theorem mul_rpow_of_ne_zero {x : ennreal} {y : ennreal} (hx : x ≠ 0) (hy : y ≠ 0) (z : ℝ) : (x * y) ^ z = x ^ z * y ^ z := sorry

theorem mul_rpow_of_nonneg (x : ennreal) (y : ennreal) {z : ℝ} (hz : 0 ≤ z) : (x * y) ^ z = x ^ z * y ^ z := sorry

theorem inv_rpow_of_pos {x : ennreal} {y : ℝ} (hy : 0 < y) : x⁻¹ ^ y = (x ^ y⁻¹) := sorry

theorem div_rpow_of_nonneg (x : ennreal) (y : ennreal) {z : ℝ} (hz : 0 ≤ z) : (x / y) ^ z = x ^ z / y ^ z := sorry

theorem rpow_le_rpow {x : ennreal} {y : ennreal} {z : ℝ} (h₁ : x ≤ y) (h₂ : 0 ≤ z) : x ^ z ≤ y ^ z := sorry

theorem rpow_lt_rpow {x : ennreal} {y : ennreal} {z : ℝ} (h₁ : x < y) (h₂ : 0 < z) : x ^ z < y ^ z := sorry

theorem rpow_le_rpow_iff {x : ennreal} {y : ennreal} {z : ℝ} (hz : 0 < z) : x ^ z ≤ y ^ z ↔ x ≤ y := sorry

theorem rpow_lt_rpow_iff {x : ennreal} {y : ennreal} {z : ℝ} (hz : 0 < z) : x ^ z < y ^ z ↔ x < y := sorry

theorem le_rpow_one_div_iff {x : ennreal} {y : ennreal} {z : ℝ} (hz : 0 < z) : x ≤ y ^ (1 / z) ↔ x ^ z ≤ y := sorry

theorem lt_rpow_one_div_iff {x : ennreal} {y : ennreal} {z : ℝ} (hz : 0 < z) : x < y ^ (1 / z) ↔ x ^ z < y := sorry

theorem rpow_lt_rpow_of_exponent_lt {x : ennreal} {y : ℝ} {z : ℝ} (hx : 1 < x) (hx' : x ≠ ⊤) (hyz : y < z) : x ^ y < x ^ z := sorry

theorem rpow_le_rpow_of_exponent_le {x : ennreal} {y : ℝ} {z : ℝ} (hx : 1 ≤ x) (hyz : y ≤ z) : x ^ y ≤ x ^ z := sorry

theorem rpow_lt_rpow_of_exponent_gt {x : ennreal} {y : ℝ} {z : ℝ} (hx0 : 0 < x) (hx1 : x < 1) (hyz : z < y) : x ^ y < x ^ z := sorry

theorem rpow_le_rpow_of_exponent_ge {x : ennreal} {y : ℝ} {z : ℝ} (hx1 : x ≤ 1) (hyz : z ≤ y) : x ^ y ≤ x ^ z := sorry

theorem rpow_le_self_of_le_one {x : ennreal} {z : ℝ} (hx : x ≤ 1) (h_one_le : 1 ≤ z) : x ^ z ≤ x :=
  eq.mpr (id (congr_arg (LessEq (x ^ z)) (Eq._oldrec (Eq.refl x) (Eq.symm (rpow_one x)))))
    (rpow_le_rpow_of_exponent_ge hx h_one_le)

theorem le_rpow_self_of_one_le {x : ennreal} {z : ℝ} (hx : 1 ≤ x) (h_one_le : 1 ≤ z) : x ≤ x ^ z :=
  eq.mpr (id (congr_fun (congr_arg LessEq (Eq._oldrec (Eq.refl x) (Eq.symm (rpow_one x)))) (x ^ z)))
    (rpow_le_rpow_of_exponent_le hx h_one_le)

theorem rpow_pos_of_nonneg {p : ℝ} {x : ennreal} (hx_pos : 0 < x) (hp_nonneg : 0 ≤ p) : 0 < x ^ p := sorry

theorem rpow_pos {p : ℝ} {x : ennreal} (hx_pos : 0 < x) (hx_ne_top : x ≠ ⊤) : 0 < x ^ p := sorry

theorem rpow_lt_one {x : ennreal} {z : ℝ} (hx : x < 1) (hz : 0 < z) : x ^ z < 1 := sorry

theorem rpow_le_one {x : ennreal} {z : ℝ} (hx : x ≤ 1) (hz : 0 ≤ z) : x ^ z ≤ 1 := sorry

theorem rpow_lt_one_of_one_lt_of_neg {x : ennreal} {z : ℝ} (hx : 1 < x) (hz : z < 0) : x ^ z < 1 := sorry

theorem rpow_le_one_of_one_le_of_neg {x : ennreal} {z : ℝ} (hx : 1 ≤ x) (hz : z < 0) : x ^ z ≤ 1 := sorry

theorem one_lt_rpow {x : ennreal} {z : ℝ} (hx : 1 < x) (hz : 0 < z) : 1 < x ^ z := sorry

theorem one_le_rpow {x : ennreal} {z : ℝ} (hx : 1 ≤ x) (hz : 0 < z) : 1 ≤ x ^ z := sorry

theorem one_lt_rpow_of_pos_of_lt_one_of_neg {x : ennreal} {z : ℝ} (hx1 : 0 < x) (hx2 : x < 1) (hz : z < 0) : 1 < x ^ z := sorry

theorem one_le_rpow_of_pos_of_le_one_of_neg {x : ennreal} {z : ℝ} (hx1 : 0 < x) (hx2 : x ≤ 1) (hz : z < 0) : 1 ≤ x ^ z := sorry

theorem to_nnreal_rpow (x : ennreal) (z : ℝ) : ennreal.to_nnreal x ^ z = ennreal.to_nnreal (x ^ z) := sorry

theorem to_real_rpow (x : ennreal) (z : ℝ) : ennreal.to_real x ^ z = ennreal.to_real (x ^ z) := sorry

theorem rpow_left_injective {x : ℝ} (hx : x ≠ 0) : function.injective fun (y : ennreal) => y ^ x := sorry

theorem rpow_left_surjective {x : ℝ} (hx : x ≠ 0) : function.surjective fun (y : ennreal) => y ^ x := sorry

theorem rpow_left_bijective {x : ℝ} (hx : x ≠ 0) : function.bijective fun (y : ennreal) => y ^ x :=
  { left := rpow_left_injective hx, right := rpow_left_surjective hx }

theorem rpow_left_monotone_of_nonneg {x : ℝ} (hx : 0 ≤ x) : monotone fun (y : ennreal) => y ^ x :=
  fun (y z : ennreal) (hyz : y ≤ z) => rpow_le_rpow hyz hx

theorem rpow_left_strict_mono_of_pos {x : ℝ} (hx : 0 < x) : strict_mono fun (y : ennreal) => y ^ x :=
  fun (y z : ennreal) (hyz : y < z) => rpow_lt_rpow hyz hx

end ennreal

theorem ennreal.measurable_rpow : measurable fun (p : ennreal × ℝ) => prod.fst p ^ prod.snd p := sorry

theorem measurable.ennreal_rpow {α : Type u_1} [measurable_space α] {f : α → ennreal} (hf : measurable f) {g : α → ℝ} (hg : measurable g) : measurable fun (a : α) => f a ^ g a :=
  id (measurable.comp ennreal.measurable_rpow (measurable.prod hf hg))

theorem ennreal.measurable_rpow_const {y : ℝ} : measurable fun (a : ennreal) => a ^ y :=
  id (measurable.comp ennreal.measurable_rpow (measurable.prod measurable_id (id measurable_const)))

theorem measurable.ennreal_rpow_const {α : Type u_1} [measurable_space α] {f : α → ennreal} (hf : measurable f) {y : ℝ} : measurable fun (a : α) => f a ^ y :=
  measurable.ennreal_rpow hf measurable_const

theorem ae_measurable.ennreal_rpow_const {α : Type u_1} [measurable_space α] {f : α → ennreal} {μ : measure_theory.measure α} (hf : ae_measurable f) {y : ℝ} : ae_measurable fun (a : α) => f a ^ y :=
  measurable.comp_ae_measurable ennreal.measurable_rpow_const hf


/-
The direct encoding for the at-most-one function.

Authors: Cayden Codel, Jeremy Avigad, Marijn Heule
Carnegie Mellon University
-/

import cnf.literal
import cnf.assignment
import cnf.clause
import cnf.cnf
import cnf.encoding

import cardinality.amk
import cardinality.distinct

variables {V : Type*} [decidable_eq V] [inhabited V]

open list
open nat
open amk
open clause
open assignment
open literal
open cnf
open distinct
open encoding

namespace direct_amo

def direct_amo : list (literal V) → cnf V
| []        := []
| (l :: ls) := (ls.map (λ m, [l.flip, m.flip])) ++ (direct_amo ls)

@[simp] theorem direct_amo_nil : direct_amo ([] : list (literal V)) = [] := rfl

@[simp] theorem direct_amo_singleton (lit : literal V) :
  direct_amo [lit] = [] := rfl

@[simp] theorem direct_amo_double (l₁ l₂ : literal V) :
  direct_amo [l₁, l₂] = [[l₁.flip, l₂.flip]] := rfl

theorem exists_mem_of_length_ge_two :
  ∀ {l : list (literal V)}, length l ≥ 2 → ∃ (c : clause V), c ∈ direct_amo l
| []   hl := by { rw length at hl, linarith }
| [l₁] hl := by { rw [length, length] at hl, linarith }
| (l₁ :: l₂ :: ls) hl := begin
  use [[l₁.flip, l₂.flip]],
  rw [direct_amo, mem_append], left,
  simp only [map, mem_cons_iff, eq_self_iff_true, true_or]
end

theorem length_eq_two_of_mem {l : list (literal V)} {c : clause V} (hl : length l ≥ 2) :
  c ∈ direct_amo l → length c = 2 :=
begin
  induction l with l₁ ls ih,
  { rw length at hl, linarith },
  { rw direct_amo,
    simp only [mem_map, mem_append],
    by_cases hls : length ls = 1,
    { rcases exists_of_length_succ ls hls with ⟨l₂, ls, rfl⟩,
      simp [length_eq_zero] at hls,
      simp [hls],
      rintro rfl,
      dec_trivial },
    { rw length at hl,
      have := succ_le_succ_iff.mp hl,
      have : 1 < ls.length, exact (ne.symm hls).le_iff_lt.mp this,
      have : 2 ≤ ls.length, exact succ_le_iff.mpr this,
      rintros (⟨a, ha, rfl⟩ | h),
      { dec_trivial },
      { exact ih this h } } }
end

-- TODO rename, distinct isn't used here
theorem exists_double_flip_eq_of_mem {l : list (literal V)} {c : clause V} (hl : length l ≥ 2) :
  c ∈ direct_amo l → ∃ (lit₁ lit₂ : literal V), [lit₁.flip, lit₂.flip] = c :=
begin
  intro h,
  rcases exists_cons_cons_of_length_eq_two (length_eq_two_of_mem hl h) with ⟨lit₁, lit₂, rfl⟩,
  use [lit₁.flip, lit₂.flip],
  rw [flip_flip, flip_flip]
end

-- TODO this feels like a very long proof, can probably be shortened
theorem distinct_iff_mem {l : list (literal V)} {lit₁ lit₂ : literal V} :
  distinct lit₁ lit₂ l ↔ [lit₁.flip, lit₂.flip] ∈ direct_amo l :=
begin
  split,
  { induction l with l₁ ls ih,
    { intro h, exact absurd h (not_distinct_nil _ _) },
    { intro h,
      have hdis := h,
      have hl := length_ge_two_of_distinct h,
      rcases exists_cons_cons_of_length_ge_two hl with ⟨a₁, a₂, L, hL⟩,
      rw ← (tail_eq_of_cons_eq hL) at *,
      rw [direct_amo, mem_append],
      rcases h with ⟨i, j, hi, hj, hij, hil, hjl⟩,
      cases i,
      { rw nth_le at hil,
        left,
        rw [hil, mem_map],
        use [lit₂, mem_tail_of_distinct_cons hdis] },
      { have : distinct lit₁ lit₂ (a₂ :: L),
        { rw length at hi,
          have := succ_le_iff.mpr ((gt_of_gt_of_ge hij (zero_le i.succ))),
          rcases nth_le_of_ge hj this with ⟨h₁, h₂⟩,
          rw length at h₁,
          rw distinct,
          use [i, j - 1, succ_lt_succ_iff.mp hi, succ_lt_succ_iff.mp h₁],
          split,
          { rw ← nat.sub_add_cancel this at hij,
            exact succ_lt_succ_iff.mp hij },
          rw nth_le at hil,
          rw [hjl, nth_le] at h₂,
          exact ⟨hil, h₂.symm⟩ },
        exact or.inr (ih this) } } },
  { induction l with l₁ ls ih,
    { simp only [direct_amo_nil, not_mem_nil, is_empty.forall_iff] },
    { cases ls with l₂ ls,
      { rw direct_amo_singleton, intro h, exact absurd h (not_mem_nil _) },
      cases ls with l₃ ls,
      { rw [direct_amo_double, mem_singleton],
        simp only [eq_self_iff_true, and_true, and_imp],
        intros h₁ h₂,
        rw [flip_inj.mp h₁, flip_inj.mp h₂],
        use [0, 1], simp },
      { intro h, -- TODO this feels very casewise
        rw [direct_amo, mem_append] at h,
        rcases h with (h | h),
        { simp at h,
          rcases h with (⟨h₁, h₂⟩ | ⟨h₁, h₃⟩ | ⟨l₄, hl₄, h₁, hls⟩),
          { rw [flip_inj.mp h₁, flip_inj.mp h₂],
            use [0, 1], simp },
          { rw [flip_inj.mp h₁, flip_inj.mp h₃],
            use [0, 2], simp, linarith }, -- can be done explicitly rather than linarith
          { rw [flip_inj.mp h₁, ← flip_inj.mp hls],
            rcases nth_le_of_mem hl₄ with ⟨n, hn, hln₄⟩,
            use [0, n + 3],
            simp,
            use [succ_lt_succ (succ_lt_succ (succ_lt_succ hn)), hln₄] } },
        { have : (l₂ :: l₃ :: ls).length ≥ 2,
          { rw [length, length], linarith },
          exact distinct_cons_of_distinct l₁ (ih h) } } } }
end

theorem subset_direct_of_sublist {l₁ l₂ : list (literal V)} :
  l₁ <+ l₂ → (direct_amo l₁) ⊆ (direct_amo l₂) :=
begin
  induction l₂ with lit₁ ls ih generalizing l₁,
  { rw sublist_nil_iff_eq_nil, rintro rfl, exact subset.refl _ },
  { intros hl₁ c hc,
    cases hl₁,
    { rw [direct_amo, mem_append],
      exact or.inr ((ih hl₁_ᾰ) hc) },
    { rw [direct_amo, mem_append] at hc,
      rcases hc with (hc | hc),
      { rw mem_map at hc,
        rcases hc with ⟨a, hal, hac⟩,
        rw [direct_amo, mem_append, mem_map],
        exact or.inl ⟨a, sublist.subset hl₁_ᾰ hal, hac⟩ },
      { rw [direct_amo, mem_append],
        exact or.inr (ih hl₁_ᾰ hc) } } }
end

theorem eval_tt_iff_forall_distinct_eval_ff {τ : assignment V}
  {l : list (literal V)} : length l ≥ 2 → ((direct_amo l).eval τ = tt ↔
   (∀ {lit₁ lit₂ : literal V}, (distinct lit₁ lit₂ l) → lit₁.eval τ = ff ∨ lit₂.eval τ = ff)) :=
begin
  intro hl,
  split,
  { intros heval lit₁ lit₂ hdis,
    rw cnf.eval_tt_iff_forall_clause_eval_tt at heval,
    have := heval [lit₁.flip, lit₂.flip] (distinct_iff_mem.mp hdis),
    rw eval_tt_iff_exists_literal_eval_tt at this,
    rcases this with ⟨lit, hmem, hlit⟩,
    simp only [mem_cons_iff, mem_singleton] at hmem,
    rcases hmem with (rfl | rfl),
    { rw eval_flip at hlit,
      exact or.inl (bool.eq_ff_of_bnot_eq_tt hlit) },
    { rw eval_flip at hlit,
      exact or.inr (bool.eq_ff_of_bnot_eq_tt hlit) } },
  { intro h,
    rw cnf.eval_tt_iff_forall_clause_eval_tt,
    intros c hc,
    rcases exists_double_flip_eq_of_mem hl hc with ⟨l₁, l₂, rfl⟩,
    have := h (distinct_iff_mem.mpr hc),
    rw eval_tt_iff_exists_literal_eval_tt,
    rcases h (distinct_iff_mem.mpr hc) with (hff | hff),
    { use l₁.flip,
      simp [eval_flip_of_eval hff] },
    { use l₂.flip,
      simp [eval_flip_of_eval hff] } }
end

theorem eval_tt_iff_forall_distinct_eval_ff_of_eval_tt {τ : assignment V}
  {l : list (literal V)} : length l ≥ 2 → ((direct_amo l).eval τ = tt ↔
   (∀ {lit₁ lit₂ : literal V}, (distinct lit₁ lit₂ l) → lit₁.eval τ = tt → lit₂.eval τ = ff)) :=
begin
  intro hl,
  split,
  { intros ht lit₁ lit₂ hdis hlit₁,
    rcases (eval_tt_iff_forall_distinct_eval_ff hl).mp ht hdis with (h | h),
    { rw h at hlit₁, contradiction },
    { exact h } },
  { intro hls,
    apply (eval_tt_iff_forall_distinct_eval_ff hl).mpr,
    intros lit₁ lit₂ hdis,
    cases h : (literal.eval τ lit₁),
    { exact or.inl (refl ff) },
    { exact or.inr (hls hdis h) } }
end

theorem eval_ff_iff_forall_distinct_eval_tt {τ : assignment V}
  {l : list (literal V)} (hl : length l ≥ 2) :
  ((direct_amo l).eval τ = ff ↔
  (∃ {lit₁ lit₂ : literal V}, (distinct lit₁ lit₂ l) ∧ lit₁.eval τ = tt ∧ lit₂.eval τ = tt)) :=
begin
  split,
  { rw cnf.eval_ff_iff_exists_clause_eval_ff,
    rintros ⟨c, hmem, hc⟩,
    rcases exists_double_flip_eq_of_mem hl hmem with ⟨lit₁, lit₂, rfl⟩,
    rw eval_ff_iff_forall_literal_eval_ff at hc,
    use [lit₁, lit₂, distinct_iff_mem.mpr hmem],
    have h₁ : lit₁.flip ∈ [lit₁.flip, lit₂.flip], from mem_cons_self _ _,
    have h₂ : lit₂.flip ∈ [lit₁.flip, lit₂.flip], simp,
    have h₁eval := eval_flip_of_eval (hc lit₁.flip h₁),
    have h₂eval := eval_flip_of_eval (hc lit₂.flip h₂),
    rw flip_flip at h₁eval h₂eval,
    exact ⟨h₁eval, h₂eval⟩ },
  { rintro ⟨lit₁, lit₂, hdis, h₁, h₂⟩,
    rw cnf.eval_ff_iff_exists_clause_eval_ff,
    use [[lit₁.flip, lit₂.flip], distinct_iff_mem.mp hdis],
    rw eval_ff_iff_forall_literal_eval_ff,
    simp [h₁, h₂, eval_flip] }
end

theorem direct_amo_encodes_amo : ∀ (l : list (literal V)),
  encodes (amk 1) (direct_amo l) l
| []   := by { intro τ, simp, }
| [l₁] := by { intro τ, simp [← amk.eval], use τ }
| (l₁ :: l₂ :: ls) := begin
  have hl : length (l₁ :: l₂ :: ls) ≥ 2, dec_trivial,
  intro τ, split,
  { intro hamk, rw ← amk.eval at hamk,
    rw amo_eval_tt_iff_distinct_eval_ff_of_eval_tt at hamk,
    use τ,
    split,
    { rw cnf.eval_tt_iff_forall_clause_eval_tt,
      intros c hc,
      rw eval_tt_iff_exists_literal_eval_tt,
      rcases exists_double_flip_eq_of_mem hl hc with ⟨lit₁, lit₂, rfl⟩,
      cases h : (lit₁.eval τ),
      { use [lit₁.flip, mem_cons_self _ _],
        rw eval_flip,
        simp only [h, bool.bnot_ff] },
      { have : lit₂.flip ∈ [lit₁.flip, lit₂.flip], simp,
        use [lit₂.flip, this],
        exact eval_flip_of_eval (hamk (distinct_iff_mem.mpr hc) h) } },
    { exact eqod.refl _ _ } },
  { rintros ⟨σ, heval, hsigma⟩,
    rw cnf.eval_tt_iff_forall_clause_eval_tt at heval,
    rw ← amk.eval,
    rw amo_eval_tt_iff_distinct_eval_ff_of_eval_tt,
    intros lit₁ lit₂ hdis h₁,
    have := heval _ (distinct_iff_mem.mp hdis),
    rw eval_tt_iff_exists_literal_eval_tt at this,
    rcases this with ⟨lit, hmem, hlit⟩,
    simp at hmem,
    rcases hmem with (rfl | rfl),
    { have := mem_vars_of_mem (mem_of_distinct_left hdis),
      rw eval_eq_of_eqod_of_var_mem hsigma this at h₁,
      rw [eval_flip, h₁] at hlit,
      contradiction },
    { have := mem_vars_of_mem (mem_of_distinct_right hdis),
      have h₂ := eval_flip_of_eval hlit,
      rw flip_flip at h₂,
      rw ← eval_eq_of_eqod_of_var_mem hsigma this at h₂,
      exact h₂ } }
end

/-

lemma map_vars (lit : literal V) (l : list (literal V)) : l ≠ [] →
  cnf.vars (l.map (λ m, [lit.flip, m.flip])) = clause.vars (lit :: l) :=
begin
  induction l with l ls ih,
  { simp },
  { cases ls with l₂ ls,
    { simp [clause.vars, flip_var_eq] },
    { simp at ih,
      simp [cnf.vars, clause.vars, flip_var_eq, ih],
      rw ← finset.union_assoc {l.var} {lit.var} _,
      rw finset.union_comm {l.var} {lit.var},
      rw finset.union_assoc {lit.var} {l.var} _,
      rw ← finset.union_assoc {lit.var} {lit.var} _,
      simp } }
end

theorem vars_direct_amo (l : list (literal V)) :
  length l ≥ 2 → (direct_amo l).vars = clause.vars l :=
begin
  sorry
  intro hl,
  induction l with l ls ih,
  { simp [cnf.vars_singleton, direct_amo] },
  { cases ls with l₂ ls,
    { simp at hl, exfalso, linarith },
    { rw finset.ext_iff, intro v, split,
      { intro hv,
        simp [direct_amo, cnf.vars, cnf.vars_append] at hv,
        rcases hv with (hv | hv | hv),
        { simp [clause.vars] at hv,
          rcases hv with (hv | hv);
          { rw flip_var_eq at hv,
            simp [clause.vars, hv] } },
        { cases ls with l₃ ls,
          { simp at hv, contradiction },
          { rw map_vars at hv,
            simp [clause.vars] at hv |-,
            rcases hv with (hv | hv | hv),
            { exact or.inl hv },
            { right, right, exact or.inl hv },
            { right, right, exact or.inr hv },
            simp } },
        { cases ls with l₃ ls,
          { simp at hv, contradiction },
          rcases hv with (hv | hv),
          { rw map_vars at hv,
            simp [clause.vars] at hv |-,
            rcases hv with (hv | hv | hv),
            { right, exact or.inl hv },
            { right, right, exact or.inl hv },
            { right, right, exact or.inr hv },
            simp },
          { have : 2 ≤ ls.length + 1 + 1, sorry,
            simp [this] at ih,
            simp [clause.vars],
            sorry } } },
      {

      } },
    { intro hv,
      simp [direct_amo, cnf.vars_append],
      simp [clause.vars] at hv,
      rcases hv with (rfl | hv),
      { left,
        cases ls with l₂ ls,
        {
          simp,
        },
        rw map_vars,
        simp [clause.vars], },
      { right, rw ih, exact hv } } }
end

theorem direct_amo_eq_amo' {τ : assignment nat} {l : list (literal nat)} :
  (direct_amo l).eval τ = tt ↔ amk.eval 1 τ l = tt :=
begin
  split,
  {

  }
end

theorem direct_amo_eq_amo {τ : assignment nat} {l : list (literal nat)} :
  (direct_amo l).eval τ = amk.eval 1 τ l :=
begin
  cases hd : (direct_amo l).eval τ,
  { by_contradiction,
    have := ne.symm h,
    simp at this,
    rw direct_amo_eq_amo'.mpr this at hd,
    contradiction },
  { exact (direct_amo_eq_amo'.mp hd).symm }
end
-/

end direct_amo


/-
Copyright (c) 2021 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser
-/
import linear_algebra.tensor_algebra.basic
import ring_theory.graded_algebra.basic

/-!
# Results about the grading structure of the tensor algebra

The main result is `tensor_algebra.graded_algebra`, which says that the tensor algebra is a
ℕ-graded algebra.
-/

namespace tensor_algebra
variables {R M : Type*} [comm_semiring R] [add_comm_monoid M] [module R M]

open_locale direct_sum

variables (R M)

/-- A version of `tensor_algebra.ι` that maps directly into the graded structure. This is
primarily an auxiliary construction used to provide `tensor_algebra.graded_algebra`. -/
def graded_algebra.ι : M →ₗ[R] ⨁ i : ℕ, ↥((ι R : M →ₗ[_] _).range ^ i) :=
direct_sum.lof R ℕ (λ i, ↥((ι R : M →ₗ[_] _).range ^ i)) 1
  ∘ₗ (ι R).cod_restrict _ (λ m, by simpa only [pow_one] using linear_map.mem_range_self _ m)

lemma graded_algebra.ι_apply (m : M) :
  graded_algebra.ι R M m =
    direct_sum.of (λ i, ↥((ι R : M →ₗ[_] _).range ^ i)) 1
      (⟨ι R m, by simpa only [pow_one] using linear_map.mem_range_self _ m⟩) := rfl

variables {R M}

/-- The tensor algebra is graded by the powers of the submodule `(tensor_algebra.ι R).range`. -/
instance graded_algebra :
  graded_algebra ((^) (ι R : M →ₗ[R] tensor_algebra R M).range : ℕ → submodule R _) :=
graded_algebra.of_alg_hom _
  (lift _ $ graded_algebra.ι R M)
  (begin
    ext m,
    dsimp only [linear_map.comp_apply, alg_hom.to_linear_map_apply, alg_hom.comp_apply,
      alg_hom.id_apply],
    rw [lift_ι_apply, graded_algebra.ι_apply, direct_sum.coe_alg_hom_of, subtype.coe_mk],
  end)
  (λ i x, begin
    cases x with x hx,
    dsimp only [subtype.coe_mk, direct_sum.lof_eq_of],
    refine submodule.pow_induction_on_left' _
      (λ r, _) (λ x y i hx hy ihx ihy, _) (λ m hm i x hx ih, _) hx,
    { rw [alg_hom.commutes, direct_sum.algebra_map_apply], refl },
    { rw [alg_hom.map_add, ihx, ihy, ←map_add], refl },
    { obtain ⟨_, rfl⟩ := hm,
      rw [alg_hom.map_mul, ih, lift_ι_apply, graded_algebra.ι_apply, direct_sum.of_mul_of],
      exact direct_sum.of_eq_of_graded_monoid_eq (sigma.subtype_ext (add_comm _ _) rfl) }
  end)

end tensor_algebra


/-
Copyright (c) 2020 Paul van Wamelen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul van Wamelen
-/
import number_theory.pythagorean_triples
import ring_theory.coprime.lemmas
import tactic.linear_combination

/-!
# Fermat's Last Theorem for the case n = 4
There are no non-zero integers `a`, `b` and `c` such that `a ^ 4 + b ^ 4 = c ^ 4`.
-/

noncomputable theory
open_locale classical

/-- Shorthand for three non-zero integers `a`, `b`, and `c` satisfying `a ^ 4 + b ^ 4 = c ^ 2`.
We will show that no integers satisfy this equation. Clearly Fermat's Last theorem for n = 4
follows. -/
def fermat_42 (a b c : ℤ) : Prop := a ≠ 0 ∧ b ≠ 0 ∧ a ^ 4 + b ^ 4 = c ^ 2

namespace fermat_42

lemma comm {a b c : ℤ} :
 (fermat_42 a b c) ↔ (fermat_42 b a c) :=
 by { delta fermat_42, rw add_comm, tauto }

lemma mul {a b c k : ℤ} (hk0 : k ≠ 0) :
  fermat_42 a b c ↔ fermat_42 (k * a) (k * b) (k ^ 2 * c) :=
begin
  delta fermat_42,
  split,
  { intro f42,
    split, { exact mul_ne_zero hk0 f42.1 },
    split, { exact mul_ne_zero hk0 f42.2.1 },
    { have H : a ^ 4 + b ^ 4 = c ^ 2 := f42.2.2,
      linear_combination k ^ 4 * H } },
  { intro f42,
    split, { exact right_ne_zero_of_mul f42.1 },
    split, { exact right_ne_zero_of_mul f42.2.1 },
    apply (mul_right_inj' (pow_ne_zero 4 hk0)).mp,
    linear_combination f42.2.2 }
end

lemma ne_zero {a b c : ℤ} (h : fermat_42 a b c) : c ≠ 0 :=
begin
  apply ne_zero_pow two_ne_zero _, apply ne_of_gt,
  rw [← h.2.2, (by ring : a ^ 4 + b ^ 4 = (a ^ 2) ^ 2 + (b ^ 2) ^ 2)],
  exact add_pos (sq_pos_of_ne_zero _ (pow_ne_zero 2 h.1))
      (sq_pos_of_ne_zero _ (pow_ne_zero 2 h.2.1))
end

/-- We say a solution to `a ^ 4 + b ^ 4 = c ^ 2` is minimal if there is no other solution with
a smaller `c` (in absolute value). -/
def minimal (a b c : ℤ) : Prop :=
  (fermat_42 a b c) ∧ ∀ (a1 b1 c1 : ℤ), (fermat_42 a1 b1 c1) → int.nat_abs c ≤ int.nat_abs c1

/-- if we have a solution to `a ^ 4 + b ^ 4 = c ^ 2` then there must be a minimal one. -/
lemma exists_minimal {a b c : ℤ} (h : fermat_42 a b c) :
  ∃ (a0 b0 c0), (minimal a0 b0 c0) :=
begin
  let S : set ℕ := { n | ∃ (s : ℤ × ℤ × ℤ), fermat_42 s.1 s.2.1 s.2.2 ∧ n = int.nat_abs s.2.2},
  have S_nonempty : S.nonempty,
  { use int.nat_abs c,
    rw set.mem_set_of_eq,
    use ⟨a, ⟨b, c⟩⟩, tauto },
  let m : ℕ := nat.find S_nonempty,
  have m_mem : m ∈ S := nat.find_spec S_nonempty,
  rcases m_mem with ⟨s0, hs0, hs1⟩,
  use [s0.1, s0.2.1, s0.2.2, hs0],
  intros a1 b1 c1 h1,
  rw ← hs1,
  apply nat.find_min',
  use ⟨a1, ⟨b1, c1⟩⟩, tauto
end

/-- a minimal solution to `a ^ 4 + b ^ 4 = c ^ 2` must have `a` and `b` coprime. -/
lemma coprime_of_minimal {a b c : ℤ} (h : minimal a b c) : is_coprime a b :=
begin
  apply int.gcd_eq_one_iff_coprime.mp,
  by_contradiction hab,
  obtain ⟨p, hp, hpa, hpb⟩ := nat.prime.not_coprime_iff_dvd.mp hab,
  obtain ⟨a1, rfl⟩ := (int.coe_nat_dvd_left.mpr hpa),
  obtain ⟨b1, rfl⟩ := (int.coe_nat_dvd_left.mpr hpb),
  have hpc : (p : ℤ) ^ 2 ∣ c,
  { rw [←int.pow_dvd_pow_iff zero_lt_two, ←h.1.2.2],
    apply dvd.intro (a1 ^ 4 + b1 ^ 4), ring },
  obtain ⟨c1, rfl⟩ := hpc,
  have hf : fermat_42 a1 b1 c1,
    exact (fermat_42.mul (int.coe_nat_ne_zero.mpr (nat.prime.ne_zero hp))).mpr h.1,
  apply nat.le_lt_antisymm (h.2 _ _ _ hf),
  rw [int.nat_abs_mul, lt_mul_iff_one_lt_left, int.nat_abs_pow, int.nat_abs_of_nat],
  { exact nat.one_lt_pow _ _ zero_lt_two (nat.prime.one_lt hp) },
  { exact (nat.pos_of_ne_zero (int.nat_abs_ne_zero_of_ne_zero (ne_zero hf))) },
end

/-- We can swap `a` and `b` in a minimal solution to `a ^ 4 + b ^ 4 = c ^ 2`. -/

/-- We can assume that a minimal solution to `a ^ 4 + b ^ 4 = c ^ 2` has positive `c`. -/
lemma neg_of_minimal {a b c : ℤ} :
 (minimal a b c) → (minimal a b (-c)) :=
begin
  rintros ⟨⟨ha, hb, heq⟩, h2⟩,
  split,
  { apply and.intro ha (and.intro hb _),
    rw heq, exact (neg_sq c).symm },
  rwa (int.nat_abs_neg c),
end

/-- We can assume that a minimal solution to `a ^ 4 + b ^ 4 = c ^ 2` has `a` odd. -/
lemma exists_odd_minimal {a b c : ℤ} (h : fermat_42 a b c) :
  ∃ (a0 b0 c0), (minimal a0 b0 c0) ∧ a0 % 2 = 1 :=
begin
  obtain ⟨a0, b0, c0, hf⟩ := exists_minimal h,
  cases int.mod_two_eq_zero_or_one a0 with hap hap,
  { cases int.mod_two_eq_zero_or_one b0 with hbp hbp,
    { exfalso,
      have h1 : 2 ∣ (int.gcd a0 b0 : ℤ),
      { exact int.dvd_gcd (int.dvd_of_mod_eq_zero hap) (int.dvd_of_mod_eq_zero hbp) },
        rw int.gcd_eq_one_iff_coprime.mpr (coprime_of_minimal hf) at h1, revert h1, norm_num },
    { exact ⟨b0, ⟨a0, ⟨c0, minimal_comm hf, hbp⟩⟩⟩ } },
  exact ⟨a0, ⟨b0, ⟨c0 , hf, hap⟩⟩⟩,
end

/-- We can assume that a minimal solution to `a ^ 4 + b ^ 4 = c ^ 2` has
`a` odd and `c` positive. -/
lemma exists_pos_odd_minimal {a b c : ℤ} (h : fermat_42 a b c) :
  ∃ (a0 b0 c0), (minimal a0 b0 c0) ∧ a0 % 2 = 1 ∧ 0 < c0  :=
begin
  obtain ⟨a0, b0, c0, hf, hc⟩ := exists_odd_minimal h,
  rcases lt_trichotomy 0 c0 with (h1 | rfl | h1),
  { use [a0, b0, c0], tauto },
  { exfalso, exact ne_zero hf.1 rfl},
  { use [a0, b0, -c0, neg_of_minimal hf, hc],
    exact neg_pos.mpr h1 },
end

end fermat_42

lemma int.coprime_of_sq_sum {r s : ℤ} (h2 : is_coprime s r) :
  is_coprime (r ^ 2 + s ^ 2) r :=
begin
  rw [sq, sq],
  exact (is_coprime.mul_left h2 h2).mul_add_left_left r
end

lemma int.coprime_of_sq_sum' {r s : ℤ} (h : is_coprime r s) :
  is_coprime (r ^ 2 + s ^ 2) (r * s) :=
begin
  apply is_coprime.mul_right (int.coprime_of_sq_sum (is_coprime_comm.mp h)),
  rw add_comm, apply int.coprime_of_sq_sum h
end

namespace fermat_42

-- If we have a solution to a ^ 4 + b ^ 4 = c ^ 2, we can construct a smaller one. This
-- implies there can't be a smallest solution.
lemma not_minimal {a b c : ℤ}
  (h : minimal a b c) (ha2 : a % 2 = 1) (hc : 0 < c) : false :=
begin
  -- Use the fact that a ^ 2, b ^ 2, c form a pythagorean triple to obtain m and n such that
  -- a ^ 2 = m ^ 2 - n ^ 2, b ^ 2 = 2 * m * n and c = m ^ 2 + n ^ 2
  -- first the formula:
  have ht : pythagorean_triple (a ^ 2) (b ^ 2) c,
  { delta pythagorean_triple,
    linear_combination h.1.2.2 },
  -- coprime requirement:
  have h2 : int.gcd (a ^ 2) (b ^ 2) = 1 :=
    int.gcd_eq_one_iff_coprime.mpr (coprime_of_minimal h).pow,
  -- in order to reduce the possibilities we get from the classification of pythagorean triples
  -- it helps if we know the parity of a ^ 2 (and the sign of c):
  have ha22 : a ^ 2 % 2 = 1, { rw [sq, int.mul_mod, ha2], norm_num },
  obtain ⟨m, n, ht1, ht2, ht3, ht4, ht5, ht6⟩ := ht.coprime_classification' h2 ha22 hc,
  -- Now a, n, m form a pythagorean triple and so we can obtain r and s such that
  -- a = r ^ 2 - s ^ 2, n = 2 * r * s and m = r ^ 2 + s ^ 2
  -- formula:
  have htt : pythagorean_triple a n m,
  { delta pythagorean_triple,
    linear_combination (ht1) },
  -- a and n are coprime, because a ^ 2 = m ^ 2 - n ^ 2 and m and n are coprime.
  have h3 : int.gcd a n = 1,
  { apply int.gcd_eq_one_iff_coprime.mpr,
    apply @is_coprime.of_mul_left_left _ _ _ a,
    rw [← sq, ht1, (by ring : m ^ 2 - n ^ 2 = m ^ 2 + (-n) * n)],
    exact (int.gcd_eq_one_iff_coprime.mp ht4).pow_left.add_mul_right_left (-n) },
  -- m is positive because b is non-zero and b ^ 2 = 2 * m * n and we already have 0 ≤ m.
  have hb20 : b ^ 2 ≠ 0 := mt pow_eq_zero h.1.2.1,
  have h4 : 0 < m,
  { apply lt_of_le_of_ne ht6,
    rintro rfl,
    revert hb20,
    rw ht2, simp },
  obtain ⟨r, s, htt1, htt2, htt3, htt4, htt5, htt6⟩ := htt.coprime_classification' h3 ha2 h4,
  -- Now use the fact that (b / 2) ^ 2 = m * r * s, and m, r and s are pairwise coprime to obtain
  -- i, j and k such that m = i ^ 2, r = j ^ 2 and s = k ^ 2.
  -- m and r * s are coprime because m = r ^ 2 + s ^ 2 and r and s are coprime.
  have hcp : int.gcd m (r * s) = 1,
  { rw htt3,
    exact int.gcd_eq_one_iff_coprime.mpr (int.coprime_of_sq_sum'
      (int.gcd_eq_one_iff_coprime.mp htt4)) },
  -- b is even because b ^ 2 = 2 * m * n.
  have hb2 : 2 ∣ b,
  { apply @int.prime.dvd_pow' _ 2 _ (by norm_num : nat.prime 2),
    rw [ht2, mul_assoc], exact dvd_mul_right 2 (m * n) },
  cases hb2 with b' hb2',
  have hs : b' ^ 2 = m * (r * s),
  { apply (mul_right_inj' (by norm_num : (4 : ℤ) ≠ 0)).mp,
    linear_combination (- b - 2 * b') * hb2' + ht2 + 2 * m * htt2 },
  have hrsz : r * s ≠ 0, -- because b ^ 2 is not zero and (b / 2) ^ 2 = m * (r * s)
  { by_contradiction hrsz,
    revert hb20, rw [ht2, htt2, mul_assoc, @mul_assoc _ _ _ r s, hrsz],
    simp },
  have h2b0 : b' ≠ 0,
  { apply ne_zero_pow two_ne_zero,
    rw hs, apply mul_ne_zero, { exact ne_of_gt h4}, { exact hrsz } },
  obtain ⟨i, hi⟩ := int.sq_of_gcd_eq_one hcp hs.symm,
  -- use m is positive to exclude m = - i ^ 2
  have hi' : ¬ m = - i ^ 2,
  { by_contradiction h1,
    have hit : - i ^ 2 ≤ 0, apply neg_nonpos.mpr (sq_nonneg i),
    rw ← h1 at hit,
    apply absurd h4 (not_lt.mpr hit) },
  replace hi : m = i ^ 2, { apply or.resolve_right hi hi' },
  rw mul_comm at hs,
  rw [int.gcd_comm] at hcp,
  -- obtain d such that r * s = d ^ 2
  obtain ⟨d, hd⟩ := int.sq_of_gcd_eq_one hcp hs.symm,
  -- (b / 2) ^ 2 and m are positive so r * s is positive
  have hd' : ¬ r * s = - d ^ 2,
  { by_contradiction h1,
    rw h1 at hs,
    have h2 : b' ^ 2 ≤ 0,
    { rw [hs, (by ring : - d ^ 2 * m = - (d ^ 2 * m))],
      exact neg_nonpos.mpr ((zero_le_mul_right h4).mpr (sq_nonneg d)) },
    have h2' : 0 ≤ b' ^ 2, { apply sq_nonneg b' },
    exact absurd (lt_of_le_of_ne h2' (ne.symm (pow_ne_zero _ h2b0))) (not_lt.mpr h2) },
  replace hd : r * s = d ^ 2, { apply or.resolve_right hd hd' },
  -- r = +/- j ^ 2
  obtain ⟨j, hj⟩ := int.sq_of_gcd_eq_one htt4 hd,
  have hj0 : j ≠ 0,
  { intro h0, rw [h0, zero_pow zero_lt_two, neg_zero, or_self] at hj,
    apply left_ne_zero_of_mul hrsz hj },
  rw mul_comm at hd,
  rw [int.gcd_comm] at htt4,
  -- s = +/- k ^ 2
  obtain ⟨k, hk⟩ := int.sq_of_gcd_eq_one htt4 hd,
  have hk0 : k ≠ 0,
  { intro h0, rw [h0, zero_pow zero_lt_two, neg_zero, or_self] at hk,
    apply right_ne_zero_of_mul hrsz hk },
  have hj2 : r ^ 2 = j ^ 4, { cases hj with hjp hjp; { rw hjp, ring } },
  have hk2 : s ^ 2 = k ^ 4, { cases hk with hkp hkp; { rw hkp, ring } },
  -- from m = r ^ 2 + s ^ 2 we now get a new solution to a ^ 4 + b ^ 4 = c ^ 2:
  have hh : i ^ 2 = j ^ 4 + k ^ 4, { rw [← hi, htt3, hj2, hk2] },
  have hn : n ≠ 0, { rw ht2 at hb20, apply right_ne_zero_of_mul hb20 },
  -- and it has a smaller c: from c = m ^ 2 + n ^ 2 we see that m is smaller than c, and i ^ 2 = m.
  have hic : int.nat_abs i < int.nat_abs c,
  { apply int.coe_nat_lt.mp, rw ← (int.eq_nat_abs_of_zero_le (le_of_lt hc)),
    apply gt_of_gt_of_ge _ (int.abs_le_self_sq i),
    rw [← hi, ht3],
    apply gt_of_gt_of_ge _ (int.le_self_sq m),
    exact lt_add_of_pos_right (m ^ 2) (sq_pos_of_ne_zero n hn) },
  have hic' : int.nat_abs c ≤ int.nat_abs i,
  { apply (h.2 j k i),
    exact ⟨hj0, hk0, hh.symm⟩ },
  apply absurd (not_le_of_lt hic) (not_not.mpr hic')
end

end fermat_42

lemma not_fermat_42 {a b c : ℤ} (ha : a ≠ 0) (hb : b ≠ 0) :
  a ^ 4 + b ^ 4 ≠ c ^ 2 :=
begin
  intro h,
  obtain ⟨a0, b0, c0, ⟨hf, h2, hp⟩⟩ :=
    fermat_42.exists_pos_odd_minimal (and.intro ha (and.intro hb h)),
  apply fermat_42.not_minimal hf h2 hp
end

theorem not_fermat_4 {a b c : ℤ} (ha : a ≠ 0) (hb : b ≠ 0) :
  a ^ 4 + b ^ 4 ≠ c ^ 4 :=
begin
  intro heq,
  apply @not_fermat_42 _ _ (c ^ 2) ha hb,
  rw heq, ring
end