import category_theory.category
import category_theory.functor
import category_theory.types

import tactic.tidy

import set_category.diagram_lemmas
import set_category.category_set
import help_functions

namespace em_square

universes v u

open classical
     function
     set
     category_theory
     help_functions
     diagram_lemmas
     category_set

local notation f ` ⊚ `:80 g:80 := category_struct.comp g f

/--
    Given an E-M-Square
    and that a diagonal d exists that makes one triangle commute
    then so does the other triangle and d is unique
-/
lemma commutative_triangles_epi
    {C : Type v} [category C]
        {X Y Z U : C}
          (e : X ⟶ Y)
          (f : Y ⟶ U)
          (g : X ⟶ Z)
          (m : Z ⟶ U)
          (h : f ⊚ e = m ⊚ g)
          (d : Y ⟶ Z)
          [epi e] :
            (g = d ⊚ e) →
            (∀ d₁ : Y ⟶ Z ,
                g = d₁ ⊚ e → d₁ = d)
                ∧ f = m ⊚ d
        :=
    begin
        intros g_ed,
        split,

        intros d₁ g_ed₁,
        exact eq.symm (right_cancel e (g_ed ▸ g_ed₁)),

        have ef_edm : f ⊚ e = m ⊚ d ⊚ e := by simp [g_ed, h],
        exact right_cancel e ef_edm
    end

lemma commutative_triangles_mono
        {C : Type v} [category C] {X Y Z U : C}
          (e : X ⟶ Y) (f : Y ⟶ U)
          (g : X ⟶ Z) (m : Z ⟶ U)
          (h : f ⊚ e = m ⊚ g) (d : Y ⟶ Z)
          [mono m] :
    f = m ⊚ d → ((∀ d₁ : Y ⟶ Z ,
                        f = m ⊚ d₁ → d₁ = d)
                  ∧ g = d ⊚ e)
        :=
    begin
        assume f_dm,

        split,

        assume d₁ f_dm₁,
        exact eq.symm (left_cancel m (f_dm ▸ f_dm₁)),

        have edm_gm : m ⊚ d ⊚ e = m ⊚ g := f_dm ▸ h,
        have edm_gm1: m ⊚ (d ⊚ e) = m ⊚ g := by tidy,
        exact eq.symm (left_cancel m edm_gm1)
    end

lemma commutative_triangles {X Y Z U : Type u}
          (e : X ⟶ Y)
          (f : Y ⟶ U)
          (g : X ⟶ Z)
          (m : Z ⟶ U)
          (h : f ⊚ e = m ⊚ g)
          (d : Y ⟶ Z) :
            ((epi e ∧ g = d ⊚ e) →
            ((∀ d₁ : Y ⟶ Z ,
                g = d₁ ⊚ e → d₁ = d)
                ∧ f =  m ⊚ d)) ∧
            ((mono m ∧ f = m ⊚ d) →
            ((∀ d₁ : Y ⟶ Z ,
                f =  m ⊚ d₁ → d₁ = d)
                ∧ g = d ⊚ e))
        :=
    and.intro
    (assume ⟨ep, g_ed⟩,
        begin
            have left_cancel : _ := ep.left_cancellation,
            split,

            assume d₁ g_ed₁,
            exact eq.symm (left_cancel d d₁ (g_ed ▸ g_ed₁)),

            have ef_edm : f ⊚ e = m ⊚ d ⊚ e := by simp [g_ed, h],
            exact left_cancel f (m ⊚ d) ef_edm
        end
    )
    (assume ⟨ mo, f_dm⟩ ,
        begin
            have right_cancel : _ := mo.right_cancellation,
            split,

            assume d₁ f_dm₁,
            exact eq.symm (right_cancel d d₁ (f_dm ▸ f_dm₁)),

            have edm_gm : m ⊚ d ⊚ e = m ⊚ g :=
                f_dm ▸ h,
            exact eq.symm (right_cancel (d ⊚ e) g edm_gm)
        end
    )

lemma E_M_square {X Y Z U : Type u}
          (e : X ⟶ Y) (ep : epi e)
          (f : Y ⟶ U) (g : X ⟶ Z)
          (m : Z ⟶ U) (mo : mono m)
          (h : f ⊚ e = m ⊚ g) :
          ∃! d : Y ⟶ Z , (g = d ⊚ e ∧
                          f = m ⊚ d) :=
begin
    have sur : surjective e := (epi_iff_surjective e).1 ep,
    have inj : injective m  := (mono_iff_injective m).1 mo,

    have range_f_m : range f ⊆ range m :=
        calc range f = range (f ⊚ e)    : eq_range_if_surjective e f sur
                ...  = range (m ⊚ g)    : by rw h
                ...  ⊆ range m           : range_comp_subset_range g m,

    have kern_e_g : sub_kern e g :=
        sub_kern_if_injective e f g m h inj,

    cases ((diagram_injective m f inj).2 range_f_m) with d₁ ex_uni1,
    cases ((diagram_surjective e g sur).2 kern_e_g)  with d₂ ex_uni2,

    have ex1 : m ∘ d₁ = f := ex_uni1.1,
    have ex2 : d₂ ∘ e = g := ex_uni2.1,

    have uni1 : ∀ d : Y ⟶ Z,  m ∘ d = f → d = d₁ := ex_uni1.2,

    have em_mono : _ := (commutative_triangles e f g m h d₁),

    have h1 : (∀ (d₁₁ : Y ⟶ Z), f = m ⊚ d₁₁ → d₁₁ = d₁)
                     ∧ g = d₁ ⊚ e :=

        and.right em_mono ⟨mo , eq.symm ex1⟩,
    have h2 : ∀ (d₁₁  : Y ⟶ Z), f = m ⊚ d₁₁ → d₁₁ = d₁ :=
        assume d₁₁, and.left h1 d₁₁,

    have em_epi :=
        (commutative_triangles e f g m h d₂).1 ⟨ep ,eq.symm ex2⟩,

    have h4 : ∀ (d₁₁  : Y ⟶ Z), g =  d₁₁ ⊚ e → d₁₁ = d₂ :=
        assume d₁₁, and.left em_epi d₁₁,

    have d11 : g = d₁ ⊚ e := and.right h1,
    have d22 : f = m ⊚ d₂ := and.right em_epi,

    have d1_d2 : d₂ = d₁ := and.left h1 d₂ d22,
    have d12 : f = m ⊚ d₁ := eq.subst d1_d2 d22,

    have h5 : ∀ dₓ : Y ⟶ Z ,
            (g = dₓ ⊚ e ∧ f = m ⊚ dₓ) → dₓ = d₁ :=
            assume dₓ ged_fdm,
            uni1 dₓ (eq.symm ged_fdm.2),

    exact exists_unique.intro d₁ ⟨d11 , d12⟩ h5
end

end em_square


/-
Copyright (c) 2018 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon, Patrick Massot

! This file was ported from Lean 3 source module algebra.module.pi
! leanprover-community/mathlib commit a437a2499163d85d670479f69f625f461cc5fef9
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.Module.Basic
import Mathlib.Algebra.Regular.SMul
import Mathlib.Algebra.Ring.Pi
import Mathlib.GroupTheory.GroupAction.Pi

/-!
# Pi instances for modules

This file defines instances for module and related structures on Pi Types
-/

universe u v w

variable {I : Type u}

-- The indexing type
variable {f : I → Type v}

-- The family of types already equipped with instances
variable (x y : ∀ i, f i) (i : I)

namespace Pi

theorem IsSMulRegular.pi {α : Type _} [∀ i, SMul α <| f i] {k : α}
    (hk : ∀ i, IsSMulRegular (f i) k) : IsSMulRegular (∀ i, f i) k := fun _ _ h =>
  funext fun i => hk i (congr_fun h i : _)
#align is_smul_regular.pi Pi.IsSMulRegular.pi

instance smulWithZero (α) [Zero α] [∀ i, Zero (f i)] [∀ i, SMulWithZero α (f i)] :
    SMulWithZero α (∀ i, f i) :=
  { Pi.instSMul with
    smul_zero := fun _ => funext fun _ => smul_zero _
    zero_smul := fun _ => funext fun _ => zero_smul _ _ }
#align pi.smul_with_zero Pi.smulWithZero

instance smulWithZero' {g : I → Type _} [∀ i, Zero (g i)] [∀ i, Zero (f i)]
    [∀ i, SMulWithZero (g i) (f i)] : SMulWithZero (∀ i, g i) (∀ i, f i) :=
  { Pi.smul' with
    smul_zero := fun _ => funext fun _ => smul_zero _
    zero_smul := fun _ => funext fun _ => zero_smul _ _ }
#align pi.smul_with_zero' Pi.smulWithZero'

instance mulActionWithZero (α) [MonoidWithZero α] [∀ i, Zero (f i)]
    [∀ i, MulActionWithZero α (f i)] : MulActionWithZero α (∀ i, f i) :=
  { Pi.mulAction _, Pi.smulWithZero _ with }
#align pi.mul_action_with_zero Pi.mulActionWithZero

instance mulActionWithZero' {g : I → Type _} [∀ i, MonoidWithZero (g i)] [∀ i, Zero (f i)]
    [∀ i, MulActionWithZero (g i) (f i)] : MulActionWithZero (∀ i, g i) (∀ i, f i) :=
  { Pi.mulAction', Pi.smulWithZero' with }
#align pi.mul_action_with_zero' Pi.mulActionWithZero'

variable (I f)

instance module (α) {r : Semiring α} {m : ∀ i, AddCommMonoid <| f i} [∀ i, Module α <| f i] :
    @Module α (∀ i : I, f i) r (@Pi.addCommMonoid I f m) :=
  { Pi.distribMulAction _ with
    add_smul := fun _ _ _ => funext fun _ => add_smul _ _ _
    zero_smul := fun _ => funext fun _ => zero_smul α _ }
#align pi.module Pi.module

/- Extra instance to short-circuit type class resolution.
For unknown reasons, this is necessary for certain inference problems. E.g., for this to succeed:
```lean
example (β X : Type _) [NormedAddCommGroup β] [NormedSpace ℝ β] : Module ℝ (X → β) :=
inferInstance
```
See: https://leanprover.zulipchat.com/#narrow/stream/113488-general/topic/Typeclass.20resolution.20under.20binders/near/281296989
-/
/-- A special case of `Pi.module` for non-dependent types. Lean struggles to elaborate
definitions elsewhere in the library without this. -/
instance Function.module (α β : Type _) [Semiring α] [AddCommMonoid β] [Module α β] :
    Module α (I → β) :=
  Pi.module _ _ _
#align function.module Pi.Function.module

variable {I f}

instance module' {g : I → Type _} {r : ∀ i, Semiring (f i)} {m : ∀ i, AddCommMonoid (g i)}
    [∀ i, Module (f i) (g i)] : Module (∀ i, f i) (∀ i, g i)
    where
  add_smul := by
    intros
    ext1
    apply add_smul
  zero_smul := by
    intros
    ext1
    -- Porting note: not sure why `apply zero_smul` fails here.
    rw [zero_smul]
#align pi.module' Pi.module'

instance noZeroSMulDivisors (α) {_ : Semiring α} {_ : ∀ i, AddCommMonoid <| f i}
    [∀ i, Module α <| f i] [∀ i, NoZeroSMulDivisors α <| f i] :
    NoZeroSMulDivisors α (∀ i : I, f i) :=
  ⟨fun {_ _} h =>
    or_iff_not_imp_left.mpr fun hc =>
      funext fun i => (smul_eq_zero.mp (congr_fun h i)).resolve_left hc⟩

/-- A special case of `Pi.noZeroSMulDivisors` for non-dependent types. Lean struggles to
synthesize this instance by itself elsewhere in the library. -/
instance _root_.Function.noZeroSMulDivisors {ι α β : Type _} {_ : Semiring α} {_ : AddCommMonoid β}
    [Module α β] [NoZeroSMulDivisors α β] : NoZeroSMulDivisors α (ι → β) :=
  Pi.noZeroSMulDivisors _
#align function.no_zero_smul_divisors Function.noZeroSMulDivisors

end Pi


/-
Copyright (c) 2019 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov

Some proofs and docs came from `algebra/commute` (c) Neil Strickland
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebra.group.units
import Mathlib.PostPort

universes u

namespace Mathlib

/-!
# Semiconjugate elements of a semigroup

## Main definitions

We say that `x` is semiconjugate to `y` by `a` (`semiconj_by a x y`), if `a * x = y * a`.
In this file we  provide operations on `semiconj_by _ _ _`.

In the names of these operations, we treat `a` as the “left” argument, and both `x` and `y` as
“right” arguments. This way most names in this file agree with the names of the corresponding lemmas
for `commute a b = semiconj_by a b b`. As a side effect, some lemmas have only `_right` version.

Lean does not immediately recognise these terms as equations, so for rewriting we need syntax like
`rw [(h.pow_right 5).eq]` rather than just `rw [h.pow_right 5]`.

This file provides only basic operations (`mul_left`, `mul_right`, `inv_right` etc). Other
operations (`pow_right`, field inverse etc) are in the files that define corresponding notions.
-/

/-- `x` is semiconjugate to `y` by `a`, if `a * x = y * a`. -/
def add_semiconj_by {M : Type u} [Add M] (a : M) (x : M) (y : M) := a + x = y + a

namespace semiconj_by

/-- Equality behind `semiconj_by a x y`; useful for rewriting. -/
protected theorem eq {S : Type u} [Mul S] {a : S} {x : S} {y : S} (h : semiconj_by a x y) :
    a * x = y * a :=
  h

/-- If `a` semiconjugates `x` to `y` and `x'` to `y'`,
then it semiconjugates `x * x'` to `y * y'`. -/
@[simp] theorem Mathlib.add_semiconj_by.add_right {S : Type u} [add_semigroup S] {a : S} {x : S}
    {y : S} {x' : S} {y' : S} (h : add_semiconj_by a x y) (h' : add_semiconj_by a x' y') :
    add_semiconj_by a (x + x') (y + y') :=
  sorry

/-- If both `a` and `b` semiconjugate `x` to `y`, then so does `a * b`. -/
theorem Mathlib.add_semiconj_by.add_left {S : Type u} [add_semigroup S] {a : S} {b : S} {x : S}
    {y : S} {z : S} (ha : add_semiconj_by a y z) (hb : add_semiconj_by b x y) :
    add_semiconj_by (a + b) x z :=
  sorry

/-- Any element semiconjugates `1` to `1`. -/
@[simp] theorem one_right {M : Type u} [monoid M] (a : M) : semiconj_by a 1 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (semiconj_by a 1 1)) (equations._eqn_1 a 1 1)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a * 1 = 1 * a)) (mul_one a)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (a = 1 * a)) (one_mul a))) (Eq.refl a)))

/-- One semiconjugates any element to itself. -/
@[simp] theorem one_left {M : Type u} [monoid M] (x : M) : semiconj_by 1 x x :=
  Eq.symm (one_right x)

/-- If `a` semiconjugates a unit `x` to a unit `y`, then it semiconjugates `x⁻¹` to `y⁻¹`. -/
theorem Mathlib.add_semiconj_by.units_neg_right {M : Type u} [add_monoid M] {a : M}
    {x : add_units M} {y : add_units M} (h : add_semiconj_by a ↑x ↑y) :
    add_semiconj_by a ↑(-x) ↑(-y) :=
  sorry

@[simp] theorem units_inv_right_iff {M : Type u} [monoid M] {a : M} {x : units M} {y : units M} :
    semiconj_by a ↑(x⁻¹) ↑(y⁻¹) ↔ semiconj_by a ↑x ↑y :=
  { mp := units_inv_right, mpr := units_inv_right }

/-- If a unit `a` semiconjugates `x` to `y`, then `a⁻¹` semiconjugates `y` to `x`. -/
theorem Mathlib.add_semiconj_by.units_neg_symm_left {M : Type u} [add_monoid M] {a : add_units M}
    {x : M} {y : M} (h : add_semiconj_by (↑a) x y) : add_semiconj_by (↑(-a)) y x :=
  sorry

@[simp] theorem Mathlib.add_semiconj_by.units_neg_symm_left_iff {M : Type u} [add_monoid M]
    {a : add_units M} {x : M} {y : M} : add_semiconj_by (↑(-a)) y x ↔ add_semiconj_by (↑a) x y :=
  { mp := add_semiconj_by.units_neg_symm_left, mpr := add_semiconj_by.units_neg_symm_left }

theorem Mathlib.add_semiconj_by.units_coe {M : Type u} [add_monoid M] {a : add_units M}
    {x : add_units M} {y : add_units M} (h : add_semiconj_by a x y) : add_semiconj_by ↑a ↑x ↑y :=
  congr_arg add_units.val h

theorem units_of_coe {M : Type u} [monoid M] {a : units M} {x : units M} {y : units M}
    (h : semiconj_by ↑a ↑x ↑y) : semiconj_by a x y :=
  units.ext h

@[simp] theorem units_coe_iff {M : Type u} [monoid M] {a : units M} {x : units M} {y : units M} :
    semiconj_by ↑a ↑x ↑y ↔ semiconj_by a x y :=
  { mp := units_of_coe, mpr := units_coe }

@[simp] theorem inv_right_iff {G : Type u} [group G] {a : G} {x : G} {y : G} :
    semiconj_by a (x⁻¹) (y⁻¹) ↔ semiconj_by a x y :=
  units_inv_right_iff

theorem Mathlib.add_semiconj_by.neg_right {G : Type u} [add_group G] {a : G} {x : G} {y : G} :
    add_semiconj_by a x y → add_semiconj_by a (-x) (-y) :=
  iff.mpr add_semiconj_by.neg_right_iff

@[simp] theorem inv_symm_left_iff {G : Type u} [group G] {a : G} {x : G} {y : G} :
    semiconj_by (a⁻¹) y x ↔ semiconj_by a x y :=
  units_inv_symm_left_iff

theorem Mathlib.add_semiconj_by.neg_symm_left {G : Type u} [add_group G] {a : G} {x : G} {y : G} :
    add_semiconj_by a x y → add_semiconj_by (-a) y x :=
  iff.mpr add_semiconj_by.neg_symm_left_iff

theorem Mathlib.add_semiconj_by.neg_neg_symm {G : Type u} [add_group G] {a : G} {x : G} {y : G}
    (h : add_semiconj_by a x y) : add_semiconj_by (-a) (-y) (-x) :=
  add_semiconj_by.neg_symm_left (add_semiconj_by.neg_right h)

-- this is not a simp lemma because it can be deduced from other simp lemmas

theorem inv_inv_symm_iff {G : Type u} [group G] {a : G} {x : G} {y : G} :
    semiconj_by (a⁻¹) (y⁻¹) (x⁻¹) ↔ semiconj_by a x y :=
  iff.trans inv_right_iff inv_symm_left_iff

/-- `a` semiconjugates `x` to `a * x * a⁻¹`. -/
theorem conj_mk {G : Type u} [group G] (a : G) (x : G) : semiconj_by a x (a * x * (a⁻¹)) := sorry

end semiconj_by

/-- `a` semiconjugates `x` to `a * x * a⁻¹`. -/
theorem add_units.mk_semiconj_by {M : Type u} [add_monoid M] (u : add_units M) (x : M) :
    add_semiconj_by (↑u) x (↑u + x + ↑(-u)) :=
  eq.mpr (id (add_semiconj_by.equations._eqn_1 (↑u) x (↑u + x + ↑(-u))))
    (eq.mpr
      (id
        (Eq._oldrec (Eq.refl (↑u + x = ↑u + x + ↑(-u) + ↑u))
          (add_units.neg_add_cancel_right (↑u + x) u)))
      (Eq.refl (↑u + x)))

end Mathlib


/-
Copyright (c) 2021 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker, Eric Wieser
-/
import analysis.analytic.basic
import analysis.complex.basic
import analysis.normed.field.infinite_sum
import data.nat.choose.cast
import data.finset.noncomm_prod
import topology.algebra.algebra

/-!
# Exponential in a Banach algebra

In this file, we define `exp 𝕂 : 𝔸 → 𝔸`, the exponential map in a topological algebra `𝔸` over a
field `𝕂`.

While for most interesting results we need `𝔸` to be normed algebra, we do not require this in the
definition in order to make `exp` independent of a particular choice of norm. The definition also
does not require that `𝔸` be complete, but we need to assume it for most results.

We then prove some basic results, but we avoid importing derivatives here to minimize dependencies.
Results involving derivatives and comparisons with `real.exp` and `complex.exp` can be found in
`analysis/special_functions/exponential`.

## Main results

We prove most result for an arbitrary field `𝕂`, and then specialize to `𝕂 = ℝ` or `𝕂 = ℂ`.

### General case

- `exp_add_of_commute_of_mem_ball` : if `𝕂` has characteristic zero, then given two commuting
  elements `x` and `y` in the disk of convergence, we have
  `exp 𝕂 (x+y) = (exp 𝕂 x) * (exp 𝕂 y)`
- `exp_add_of_mem_ball` : if `𝕂` has characteristic zero and `𝔸` is commutative, then given two
  elements `x` and `y` in the disk of convergence, we have
  `exp 𝕂 (x+y) = (exp 𝕂 x) * (exp 𝕂 y)`
- `exp_neg_of_mem_ball` : if `𝕂` has characteristic zero and `𝔸` is a division ring, then given an
  element `x` in the disk of convergence, we have `exp 𝕂 (-x) = (exp 𝕂 x)⁻¹`.

### `𝕂 = ℝ` or `𝕂 = ℂ`

- `exp_series_radius_eq_top` : the `formal_multilinear_series` defining `exp 𝕂` has infinite
  radius of convergence
- `exp_add_of_commute` : given two commuting elements `x` and `y`, we have
  `exp 𝕂 (x+y) = (exp 𝕂 x) * (exp 𝕂 y)`
- `exp_add` : if `𝔸` is commutative, then we have `exp 𝕂 (x+y) = (exp 𝕂 x) * (exp 𝕂 y)`
  for any `x` and `y`
- `exp_neg` : if `𝔸` is a division ring, then we have `exp 𝕂 (-x) = (exp 𝕂 x)⁻¹`.
- `exp_sum_of_commute` : the analogous result to `exp_add_of_commute` for `finset.sum`.
- `exp_sum` : the analogous result to `exp_add` for `finset.sum`.
- `exp_nsmul` : repeated addition in the domain corresponds to repeated multiplication in the
  codomain.
- `exp_zsmul` : repeated addition in the domain corresponds to repeated multiplication in the
  codomain.

### Other useful compatibility results

- `exp_eq_exp` : if `𝔸` is a normed algebra over two fields `𝕂` and `𝕂'`, then `exp 𝕂 = exp 𝕂' 𝔸`

-/

open filter is_R_or_C continuous_multilinear_map normed_field asymptotics
open_locale nat topology big_operators ennreal

section topological_algebra

variables (𝕂 𝔸 : Type*) [field 𝕂] [ring 𝔸] [algebra 𝕂 𝔸] [topological_space 𝔸]
  [topological_ring 𝔸]

/-- `exp_series 𝕂 𝔸` is the `formal_multilinear_series` whose `n`-th term is the map
`(xᵢ) : 𝔸ⁿ ↦ (1/n! : 𝕂) • ∏ xᵢ`. Its sum is the exponential map `exp 𝕂 : 𝔸 → 𝔸`. -/
def exp_series : formal_multilinear_series 𝕂 𝔸 𝔸 :=
λ n, (n!⁻¹ : 𝕂) • continuous_multilinear_map.mk_pi_algebra_fin 𝕂 n 𝔸

variables {𝔸}

/-- `exp 𝕂 : 𝔸 → 𝔸` is the exponential map determined by the action of `𝕂` on `𝔸`.
It is defined as the sum of the `formal_multilinear_series` `exp_series 𝕂 𝔸`.

Note that when `𝔸 = matrix n n 𝕂`, this is the **Matrix Exponential**; see
[`analysis.normed_space.matrix_exponential`](../matrix_exponential) for lemmas specific to that
case. -/
noncomputable def exp (x : 𝔸) : 𝔸 := (exp_series 𝕂 𝔸).sum x

variables {𝕂}

lemma exp_series_apply_eq (x : 𝔸) (n : ℕ) : exp_series 𝕂 𝔸 n (λ _, x) = (n!⁻¹ : 𝕂) • x^n :=
by simp [exp_series]

lemma exp_series_apply_eq' (x : 𝔸) :
  (λ n, exp_series 𝕂 𝔸 n (λ _, x)) = (λ n, (n!⁻¹ : 𝕂) • x^n) :=
funext (exp_series_apply_eq x)

lemma exp_series_sum_eq (x : 𝔸) : (exp_series 𝕂 𝔸).sum x = ∑' (n : ℕ), (n!⁻¹ : 𝕂) • x^n :=
tsum_congr (λ n, exp_series_apply_eq x n)

lemma exp_eq_tsum : exp 𝕂 = (λ x : 𝔸, ∑' (n : ℕ), (n!⁻¹ : 𝕂) • x^n) :=
funext exp_series_sum_eq

lemma exp_series_apply_zero (n : ℕ) : exp_series 𝕂 𝔸 n (λ _, (0 : 𝔸)) = pi.single 0 1 n :=
begin
  rw exp_series_apply_eq,
  cases n,
  { rw [pow_zero, nat.factorial_zero, nat.cast_one, inv_one, one_smul, pi.single_eq_same], },
  { rw [zero_pow (nat.succ_pos _), smul_zero, pi.single_eq_of_ne (n.succ_ne_zero)], },
end

@[simp] lemma exp_zero [t2_space 𝔸] : exp 𝕂 (0 : 𝔸) = 1 :=
by simp_rw [exp_eq_tsum, ←exp_series_apply_eq, exp_series_apply_zero, tsum_pi_single]

@[simp] lemma exp_op [t2_space 𝔸] (x : 𝔸) :
  exp 𝕂 (mul_opposite.op x) = mul_opposite.op (exp 𝕂 x) :=
by simp_rw [exp, exp_series_sum_eq, ←mul_opposite.op_pow, ←mul_opposite.op_smul, tsum_op]

@[simp] lemma exp_unop [t2_space 𝔸] (x : 𝔸ᵐᵒᵖ) :
  exp 𝕂 (mul_opposite.unop x) = mul_opposite.unop (exp 𝕂 x) :=
by simp_rw [exp, exp_series_sum_eq, ←mul_opposite.unop_pow, ←mul_opposite.unop_smul, tsum_unop]

lemma star_exp [t2_space 𝔸] [star_ring 𝔸] [has_continuous_star 𝔸] (x : 𝔸) :
  star (exp 𝕂 x) = exp 𝕂 (star x) :=
by simp_rw [exp_eq_tsum, ←star_pow, ←star_inv_nat_cast_smul, ←tsum_star]

variables (𝕂)

lemma is_self_adjoint.exp [t2_space 𝔸] [star_ring 𝔸] [has_continuous_star 𝔸] {x : 𝔸}
  (h : is_self_adjoint x) :
  is_self_adjoint (exp 𝕂 x) :=
(star_exp x).trans $ h.symm ▸ rfl

lemma commute.exp_right [t2_space 𝔸] {x y : 𝔸} (h : commute x y) : commute x (exp 𝕂 y) :=
begin
  rw exp_eq_tsum,
  exact commute.tsum_right x (λ n, (h.pow_right n).smul_right _),
end

lemma commute.exp_left [t2_space 𝔸] {x y : 𝔸} (h : commute x y) : commute (exp 𝕂 x) y :=
(h.symm.exp_right 𝕂).symm

lemma commute.exp [t2_space 𝔸] {x y : 𝔸} (h : commute x y) : commute (exp 𝕂 x) (exp 𝕂 y) :=
(h.exp_left _).exp_right _

end topological_algebra

section topological_division_algebra
variables {𝕂 𝔸 : Type*} [field 𝕂] [division_ring 𝔸] [algebra 𝕂 𝔸] [topological_space 𝔸]
  [topological_ring 𝔸]

lemma exp_series_apply_eq_div (x : 𝔸) (n : ℕ) : exp_series 𝕂 𝔸 n (λ _, x) = x^n / n! :=
by rw [div_eq_mul_inv, ←(nat.cast_commute n! (x ^ n)).inv_left₀.eq, ←smul_eq_mul,
    exp_series_apply_eq, inv_nat_cast_smul_eq _ _ _ _]

lemma exp_series_apply_eq_div' (x : 𝔸) : (λ n, exp_series 𝕂 𝔸 n (λ _, x)) = (λ n, x^n / n!) :=
funext (exp_series_apply_eq_div x)

lemma exp_series_sum_eq_div (x : 𝔸) : (exp_series 𝕂 𝔸).sum x = ∑' (n : ℕ), x^n / n! :=
tsum_congr (exp_series_apply_eq_div x)

lemma exp_eq_tsum_div : exp 𝕂 = (λ x : 𝔸, ∑' (n : ℕ), x^n / n!) :=
funext exp_series_sum_eq_div

end topological_division_algebra

section normed

section any_field_any_algebra

variables {𝕂 𝔸 𝔹 : Type*} [nontrivially_normed_field 𝕂]
variables [normed_ring 𝔸] [normed_ring 𝔹] [normed_algebra 𝕂 𝔸] [normed_algebra 𝕂 𝔹]

lemma norm_exp_series_summable_of_mem_ball (x : 𝔸)
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  summable (λ n, ‖exp_series 𝕂 𝔸 n (λ _, x)‖) :=
(exp_series 𝕂 𝔸).summable_norm_apply hx

lemma norm_exp_series_summable_of_mem_ball' (x : 𝔸)
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  summable (λ n, ‖(n!⁻¹ : 𝕂) • x^n‖) :=
begin
  change summable (norm ∘ _),
  rw ← exp_series_apply_eq',
  exact norm_exp_series_summable_of_mem_ball x hx
end

section complete_algebra

variables [complete_space 𝔸]

lemma exp_series_summable_of_mem_ball (x : 𝔸)
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  summable (λ n, exp_series 𝕂 𝔸 n (λ _, x)) :=
summable_of_summable_norm (norm_exp_series_summable_of_mem_ball x hx)

lemma exp_series_summable_of_mem_ball' (x : 𝔸)
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  summable (λ n, (n!⁻¹ : 𝕂) • x^n) :=
summable_of_summable_norm (norm_exp_series_summable_of_mem_ball' x hx)

lemma exp_series_has_sum_exp_of_mem_ball (x : 𝔸)
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  has_sum (λ n, exp_series 𝕂 𝔸 n (λ _, x)) (exp 𝕂 x) :=
formal_multilinear_series.has_sum (exp_series 𝕂 𝔸) hx

lemma exp_series_has_sum_exp_of_mem_ball' (x : 𝔸)
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  has_sum (λ n, (n!⁻¹ : 𝕂) • x^n) (exp 𝕂 x):=
begin
  rw ← exp_series_apply_eq',
  exact exp_series_has_sum_exp_of_mem_ball x hx
end

lemma has_fpower_series_on_ball_exp_of_radius_pos (h : 0 < (exp_series 𝕂 𝔸).radius) :
  has_fpower_series_on_ball (exp 𝕂) (exp_series 𝕂 𝔸) 0 (exp_series 𝕂 𝔸).radius :=
(exp_series 𝕂 𝔸).has_fpower_series_on_ball h

lemma has_fpower_series_at_exp_zero_of_radius_pos (h : 0 < (exp_series 𝕂 𝔸).radius) :
  has_fpower_series_at (exp 𝕂) (exp_series 𝕂 𝔸) 0 :=
(has_fpower_series_on_ball_exp_of_radius_pos h).has_fpower_series_at

lemma continuous_on_exp :
  continuous_on (exp 𝕂 : 𝔸 → 𝔸) (emetric.ball 0 (exp_series 𝕂 𝔸).radius) :=
formal_multilinear_series.continuous_on

lemma analytic_at_exp_of_mem_ball (x : 𝔸) (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  analytic_at 𝕂 (exp 𝕂) x:=
begin
  by_cases h : (exp_series 𝕂 𝔸).radius = 0,
  { rw h at hx, exact (ennreal.not_lt_zero hx).elim },
  { have h := pos_iff_ne_zero.mpr h,
    exact (has_fpower_series_on_ball_exp_of_radius_pos h).analytic_at_of_mem hx }
end

/-- In a Banach-algebra `𝔸` over a normed field `𝕂` of characteristic zero, if `x` and `y` are
in the disk of convergence and commute, then `exp 𝕂 (x + y) = (exp 𝕂 x) * (exp 𝕂 y)`. -/
lemma exp_add_of_commute_of_mem_ball [char_zero 𝕂]
  {x y : 𝔸} (hxy : commute x y) (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius)
  (hy : y ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  exp 𝕂 (x + y) = (exp 𝕂 x) * (exp 𝕂 y) :=
begin
  rw [exp_eq_tsum, tsum_mul_tsum_eq_tsum_sum_antidiagonal_of_summable_norm
        (norm_exp_series_summable_of_mem_ball' x hx) (norm_exp_series_summable_of_mem_ball' y hy)],
  dsimp only,
  conv_lhs {congr, funext, rw [hxy.add_pow' _, finset.smul_sum]},
  refine tsum_congr (λ n, finset.sum_congr rfl $ λ kl hkl, _),
  rw [nsmul_eq_smul_cast 𝕂, smul_smul, smul_mul_smul, ← (finset.nat.mem_antidiagonal.mp hkl),
      nat.cast_add_choose, (finset.nat.mem_antidiagonal.mp hkl)],
  congr' 1,
  have : (n! : 𝕂) ≠ 0 := nat.cast_ne_zero.mpr n.factorial_ne_zero,
  field_simp [this]
end

/-- `exp 𝕂 x` has explicit two-sided inverse `exp 𝕂 (-x)`. -/
noncomputable def invertible_exp_of_mem_ball [char_zero 𝕂] {x : 𝔸}
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) : invertible (exp 𝕂 x) :=
{ inv_of := exp 𝕂 (-x),
  inv_of_mul_self := begin
    have hnx : -x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius,
    { rw [emetric.mem_ball, ←neg_zero, edist_neg_neg],
      exact hx },
    rw [←exp_add_of_commute_of_mem_ball (commute.neg_left $ commute.refl x) hnx hx, neg_add_self,
      exp_zero],
  end,
  mul_inv_of_self := begin
    have hnx : -x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius,
    { rw [emetric.mem_ball, ←neg_zero, edist_neg_neg],
      exact hx },
    rw [←exp_add_of_commute_of_mem_ball (commute.neg_right $ commute.refl x) hx hnx, add_neg_self,
      exp_zero],
  end }

lemma is_unit_exp_of_mem_ball [char_zero 𝕂] {x : 𝔸}
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) : is_unit (exp 𝕂 x) :=
@is_unit_of_invertible _ _ _ (invertible_exp_of_mem_ball hx)

lemma inv_of_exp_of_mem_ball [char_zero 𝕂] {x : 𝔸}
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) [invertible (exp 𝕂 x)] :
  ⅟(exp 𝕂 x) = exp 𝕂 (-x) :=
by { letI := invertible_exp_of_mem_ball hx, convert (rfl : ⅟(exp 𝕂 x) = _) }

/-- Any continuous ring homomorphism commutes with `exp`. -/
lemma map_exp_of_mem_ball {F} [ring_hom_class F 𝔸 𝔹] (f : F) (hf : continuous f) (x : 𝔸)
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  f (exp 𝕂 x) = exp 𝕂 (f x) :=
begin
  rw [exp_eq_tsum, exp_eq_tsum],
  refine ((exp_series_summable_of_mem_ball' _ hx).has_sum.map f hf).tsum_eq.symm.trans _,
  dsimp only [function.comp],
  simp_rw [one_div, map_inv_nat_cast_smul f 𝕂 𝕂, map_pow],
end

end complete_algebra

lemma algebra_map_exp_comm_of_mem_ball [complete_space 𝕂] (x : 𝕂)
  (hx : x ∈ emetric.ball (0 : 𝕂) (exp_series 𝕂 𝕂).radius) :
  algebra_map 𝕂 𝔸 (exp 𝕂 x) = exp 𝕂 (algebra_map 𝕂 𝔸 x) :=
map_exp_of_mem_ball _ (continuous_algebra_map 𝕂 𝔸) _ hx

end any_field_any_algebra

section any_field_division_algebra

variables {𝕂 𝔸 : Type*} [nontrivially_normed_field 𝕂] [normed_division_ring 𝔸] [normed_algebra 𝕂 𝔸]

variables (𝕂)

lemma norm_exp_series_div_summable_of_mem_ball (x : 𝔸)
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  summable (λ n, ‖x^n / n!‖) :=
begin
  change summable (norm ∘ _),
  rw ← exp_series_apply_eq_div' x,
  exact norm_exp_series_summable_of_mem_ball x hx
end

lemma exp_series_div_summable_of_mem_ball [complete_space 𝔸] (x : 𝔸)
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) : summable (λ n, x^n / n!) :=
summable_of_summable_norm (norm_exp_series_div_summable_of_mem_ball 𝕂 x hx)

lemma exp_series_div_has_sum_exp_of_mem_ball [complete_space 𝔸] (x : 𝔸)
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) : has_sum (λ n, x^n / n!) (exp 𝕂 x) :=
begin
  rw ← exp_series_apply_eq_div' x,
  exact exp_series_has_sum_exp_of_mem_ball x hx
end

variables {𝕂}

lemma exp_neg_of_mem_ball [char_zero 𝕂] [complete_space 𝔸] {x : 𝔸}
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  exp 𝕂 (-x) = (exp 𝕂 x)⁻¹ :=
begin
  letI := invertible_exp_of_mem_ball hx,
  exact inv_of_eq_inv (exp 𝕂 x),
end

end any_field_division_algebra

section any_field_comm_algebra

variables {𝕂 𝔸 : Type*} [nontrivially_normed_field 𝕂] [normed_comm_ring 𝔸] [normed_algebra 𝕂 𝔸]
  [complete_space 𝔸]

/-- In a commutative Banach-algebra `𝔸` over a normed field `𝕂` of characteristic zero,
`exp 𝕂 (x+y) = (exp 𝕂 x) * (exp 𝕂 y)` for all `x`, `y` in the disk of convergence. -/
lemma exp_add_of_mem_ball [char_zero 𝕂] {x y : 𝔸}
  (hx : x ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius)
  (hy : y ∈ emetric.ball (0 : 𝔸) (exp_series 𝕂 𝔸).radius) :
  exp 𝕂 (x + y) = (exp 𝕂 x) * (exp 𝕂 y) :=
exp_add_of_commute_of_mem_ball (commute.all x y) hx hy

end any_field_comm_algebra

section is_R_or_C

section any_algebra

variables (𝕂 𝔸 𝔹 : Type*) [is_R_or_C 𝕂] [normed_ring 𝔸] [normed_algebra 𝕂 𝔸]
variables [normed_ring 𝔹] [normed_algebra 𝕂 𝔹]

/-- In a normed algebra `𝔸` over `𝕂 = ℝ` or `𝕂 = ℂ`, the series defining the exponential map
has an infinite radius of convergence. -/
lemma exp_series_radius_eq_top : (exp_series 𝕂 𝔸).radius = ∞ :=
begin
  refine (exp_series 𝕂 𝔸).radius_eq_top_of_summable_norm (λ r, _),
  refine summable_of_norm_bounded_eventually _ (real.summable_pow_div_factorial r) _,
  filter_upwards [eventually_cofinite_ne 0] with n hn,
  rw [norm_mul, norm_norm (exp_series 𝕂 𝔸 n), exp_series, norm_smul, norm_inv, norm_pow,
      nnreal.norm_eq, norm_eq_abs, abs_cast_nat, mul_comm, ←mul_assoc, ←div_eq_mul_inv],
  have : ‖continuous_multilinear_map.mk_pi_algebra_fin 𝕂 n 𝔸‖ ≤ 1 :=
    norm_mk_pi_algebra_fin_le_of_pos (nat.pos_of_ne_zero hn),
  exact mul_le_of_le_one_right (div_nonneg (pow_nonneg r.coe_nonneg n) n!.cast_nonneg) this
end

lemma exp_series_radius_pos : 0 < (exp_series 𝕂 𝔸).radius :=
begin
  rw exp_series_radius_eq_top,
  exact with_top.zero_lt_top
end

variables {𝕂 𝔸 𝔹}

lemma norm_exp_series_summable (x : 𝔸) : summable (λ n, ‖exp_series 𝕂 𝔸 n (λ _, x)‖) :=
norm_exp_series_summable_of_mem_ball x ((exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)

lemma norm_exp_series_summable' (x : 𝔸) : summable (λ n, ‖(n!⁻¹ : 𝕂) • x^n‖) :=
norm_exp_series_summable_of_mem_ball' x ((exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)

section complete_algebra

variables [complete_space 𝔸]

lemma exp_series_summable (x : 𝔸) : summable (λ n, exp_series 𝕂 𝔸 n (λ _, x)) :=
summable_of_summable_norm (norm_exp_series_summable x)

lemma exp_series_summable' (x : 𝔸) : summable (λ n, (n!⁻¹ : 𝕂) • x^n) :=
summable_of_summable_norm (norm_exp_series_summable' x)

lemma exp_series_has_sum_exp (x : 𝔸) : has_sum (λ n, exp_series 𝕂 𝔸 n (λ _, x)) (exp 𝕂 x) :=
exp_series_has_sum_exp_of_mem_ball x ((exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)

lemma exp_series_has_sum_exp' (x : 𝔸) : has_sum (λ n, (n!⁻¹ : 𝕂) • x^n) (exp 𝕂 x):=
exp_series_has_sum_exp_of_mem_ball' x ((exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)

lemma exp_has_fpower_series_on_ball :
  has_fpower_series_on_ball (exp 𝕂) (exp_series 𝕂 𝔸) 0 ∞ :=
exp_series_radius_eq_top 𝕂 𝔸 ▸
  has_fpower_series_on_ball_exp_of_radius_pos (exp_series_radius_pos _ _)

lemma exp_has_fpower_series_at_zero :
  has_fpower_series_at (exp 𝕂) (exp_series 𝕂 𝔸) 0 :=
exp_has_fpower_series_on_ball.has_fpower_series_at

lemma exp_continuous : continuous (exp 𝕂 : 𝔸 → 𝔸) :=
begin
  rw [continuous_iff_continuous_on_univ, ← metric.eball_top_eq_univ (0 : 𝔸),
      ← exp_series_radius_eq_top 𝕂 𝔸],
  exact continuous_on_exp
end

lemma exp_analytic (x : 𝔸) :
  analytic_at 𝕂 (exp 𝕂) x :=
analytic_at_exp_of_mem_ball x ((exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)

/-- In a Banach-algebra `𝔸` over `𝕂 = ℝ` or `𝕂 = ℂ`, if `x` and `y` commute, then
`exp 𝕂 (x+y) = (exp 𝕂 x) * (exp 𝕂 y)`. -/
lemma exp_add_of_commute
  {x y : 𝔸} (hxy : commute x y) :
  exp 𝕂 (x + y) = (exp 𝕂 x) * (exp 𝕂 y) :=
exp_add_of_commute_of_mem_ball hxy ((exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)
  ((exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)

section
variables (𝕂)

/-- `exp 𝕂 x` has explicit two-sided inverse `exp 𝕂 (-x)`. -/
noncomputable def invertible_exp (x : 𝔸) : invertible (exp 𝕂 x) :=
invertible_exp_of_mem_ball $ (exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _

lemma is_unit_exp (x : 𝔸) : is_unit (exp 𝕂 x) :=
is_unit_exp_of_mem_ball $ (exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _

lemma inv_of_exp (x : 𝔸) [invertible (exp 𝕂 x)] :
  ⅟(exp 𝕂 x) = exp 𝕂 (-x) :=
inv_of_exp_of_mem_ball $ (exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _

lemma ring.inverse_exp (x : 𝔸) : ring.inverse (exp 𝕂 x) = exp 𝕂 (-x) :=
begin
  letI := invertible_exp 𝕂 x,
  exact ring.inverse_invertible _,
end

lemma exp_mem_unitary_of_mem_skew_adjoint [star_ring 𝔸] [has_continuous_star 𝔸] {x : 𝔸}
  (h : x ∈ skew_adjoint 𝔸) :
  exp 𝕂 x ∈ unitary 𝔸 :=
by rw [unitary.mem_iff, star_exp, skew_adjoint.mem_iff.mp h,
  ←exp_add_of_commute (commute.refl x).neg_left, ←exp_add_of_commute (commute.refl x).neg_right,
  add_left_neg, add_right_neg, exp_zero, and_self]

end

/-- In a Banach-algebra `𝔸` over `𝕂 = ℝ` or `𝕂 = ℂ`, if a family of elements `f i` mutually
commute then `exp 𝕂 (∑ i, f i) = ∏ i, exp 𝕂 (f i)`. -/
lemma exp_sum_of_commute {ι} (s : finset ι) (f : ι → 𝔸)
  (h : (s : set ι).pairwise $ λ i j, commute (f i) (f j)) :
  exp 𝕂 (∑ i in s, f i) = s.noncomm_prod (λ i, exp 𝕂 (f i))
    (λ i hi j hj _, (h.of_refl hi hj).exp 𝕂) :=
begin
  classical,
  induction s using finset.induction_on with a s ha ih,
  { simp },
  rw [finset.noncomm_prod_insert_of_not_mem _ _ _ _ ha, finset.sum_insert ha,
      exp_add_of_commute, ih (h.mono $ finset.subset_insert _ _)],
  refine commute.sum_right _ _ _ (λ i hi, _),
  exact h.of_refl (finset.mem_insert_self _ _) (finset.mem_insert_of_mem hi),
end

lemma exp_nsmul (n : ℕ) (x : 𝔸) :
  exp 𝕂 (n • x) = exp 𝕂 x ^ n :=
begin
  induction n with n ih,
  { rw [zero_smul, pow_zero, exp_zero], },
  { rw [succ_nsmul, pow_succ, exp_add_of_commute ((commute.refl x).smul_right n), ih] }
end

variables (𝕂)

/-- Any continuous ring homomorphism commutes with `exp`. -/
lemma map_exp {F} [ring_hom_class F 𝔸 𝔹] (f : F) (hf : continuous f) (x : 𝔸) :
  f (exp 𝕂 x) = exp 𝕂 (f x) :=
map_exp_of_mem_ball f hf x $ (exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _

lemma exp_smul {G} [monoid G] [mul_semiring_action G 𝔸] [has_continuous_const_smul G 𝔸]
  (g : G) (x : 𝔸) :
  exp 𝕂 (g • x) = g • exp 𝕂 x :=
(map_exp 𝕂 (mul_semiring_action.to_ring_hom G 𝔸 g) (continuous_const_smul _) x).symm

lemma exp_units_conj (y : 𝔸ˣ) (x : 𝔸)  :
  exp 𝕂 (y * x * ↑(y⁻¹) : 𝔸) = y * exp 𝕂 x * ↑(y⁻¹) :=
exp_smul _ (conj_act.to_conj_act y) x

lemma exp_units_conj' (y : 𝔸ˣ) (x : 𝔸)  :
  exp 𝕂 (↑(y⁻¹) * x * y) = ↑(y⁻¹) * exp 𝕂 x * y :=
exp_units_conj _ _ _

@[simp] lemma prod.fst_exp [complete_space 𝔹] (x : 𝔸 × 𝔹) : (exp 𝕂 x).fst = exp 𝕂 x.fst :=
map_exp _ (ring_hom.fst 𝔸 𝔹) continuous_fst x

@[simp] lemma prod.snd_exp [complete_space 𝔹] (x : 𝔸 × 𝔹) : (exp 𝕂 x).snd = exp 𝕂 x.snd :=
map_exp _ (ring_hom.snd 𝔸 𝔹) continuous_snd x

@[simp] lemma pi.exp_apply {ι : Type*} {𝔸 : ι → Type*} [fintype ι]
  [Π i, normed_ring (𝔸 i)] [Π i, normed_algebra 𝕂 (𝔸 i)] [Π i, complete_space (𝔸 i)]
  (x : Π i, 𝔸 i) (i : ι) :
  exp 𝕂 x i = exp 𝕂 (x i) :=
begin
  -- Lean struggles to infer this instance due to it wanting `[Π i, semi_normed_ring (𝔸 i)]`
  letI : normed_algebra 𝕂 (Π i, 𝔸 i) := pi.normed_algebra _,
  exact map_exp _ (pi.eval_ring_hom 𝔸 i) (continuous_apply _) x
end

lemma pi.exp_def {ι : Type*} {𝔸 : ι → Type*} [fintype ι]
  [Π i, normed_ring (𝔸 i)] [Π i, normed_algebra 𝕂 (𝔸 i)] [Π i, complete_space (𝔸 i)]
  (x : Π i, 𝔸 i) :
  exp 𝕂 x = λ i, exp 𝕂 (x i) :=
funext $ pi.exp_apply 𝕂 x

lemma function.update_exp {ι : Type*} {𝔸 : ι → Type*} [fintype ι] [decidable_eq ι]
  [Π i, normed_ring (𝔸 i)] [Π i, normed_algebra 𝕂 (𝔸 i)] [Π i, complete_space (𝔸 i)]
  (x : Π i, 𝔸 i) (j : ι) (xj : 𝔸 j) :
  function.update (exp 𝕂 x) j (exp 𝕂 xj) = exp 𝕂 (function.update x j xj) :=
begin
  ext i,
  simp_rw [pi.exp_def],
  exact (function.apply_update (λ i, exp 𝕂) x j xj i).symm,
end

end complete_algebra

lemma algebra_map_exp_comm (x : 𝕂) :
  algebra_map 𝕂 𝔸 (exp 𝕂 x) = exp 𝕂 (algebra_map 𝕂 𝔸 x) :=
algebra_map_exp_comm_of_mem_ball x $
  (exp_series_radius_eq_top 𝕂 𝕂).symm ▸ edist_lt_top _ _

end any_algebra

section division_algebra

variables {𝕂 𝔸 : Type*} [is_R_or_C 𝕂] [normed_division_ring 𝔸] [normed_algebra 𝕂 𝔸]

variables (𝕂)

lemma norm_exp_series_div_summable (x : 𝔸) : summable (λ n, ‖x^n / n!‖) :=
norm_exp_series_div_summable_of_mem_ball 𝕂 x
  ((exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)

variables [complete_space 𝔸]

lemma exp_series_div_summable (x : 𝔸) : summable (λ n, x^n / n!) :=
summable_of_summable_norm (norm_exp_series_div_summable 𝕂 x)

lemma exp_series_div_has_sum_exp (x : 𝔸) : has_sum (λ n, x^n / n!) (exp 𝕂 x):=
exp_series_div_has_sum_exp_of_mem_ball 𝕂 x
  ((exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)

variables {𝕂}

lemma exp_neg (x : 𝔸) : exp 𝕂 (-x) = (exp 𝕂 x)⁻¹ :=
exp_neg_of_mem_ball $ (exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _

lemma exp_zsmul (z : ℤ) (x : 𝔸) : exp 𝕂 (z • x) = (exp 𝕂 x) ^ z :=
begin
  obtain ⟨n, rfl | rfl⟩ := z.eq_coe_or_neg,
  { rw [zpow_coe_nat, coe_nat_zsmul, exp_nsmul] },
  { rw [zpow_neg, zpow_coe_nat, neg_smul, exp_neg, coe_nat_zsmul, exp_nsmul] },
end

lemma exp_conj (y : 𝔸) (x : 𝔸) (hy : y ≠ 0) :
  exp 𝕂 (y * x * y⁻¹) = y * exp 𝕂 x * y⁻¹ :=
exp_units_conj _ (units.mk0 y hy) x

lemma exp_conj' (y : 𝔸) (x : 𝔸)  (hy : y ≠ 0) :
  exp 𝕂 (y⁻¹ * x * y) = y⁻¹ * exp 𝕂 x * y :=
exp_units_conj' _ (units.mk0 y hy) x

end division_algebra

section comm_algebra

variables {𝕂 𝔸 : Type*} [is_R_or_C 𝕂] [normed_comm_ring 𝔸] [normed_algebra 𝕂 𝔸] [complete_space 𝔸]

/-- In a commutative Banach-algebra `𝔸` over `𝕂 = ℝ` or `𝕂 = ℂ`,
`exp 𝕂 (x+y) = (exp 𝕂 x) * (exp 𝕂 y)`. -/
lemma exp_add {x y : 𝔸} : exp 𝕂 (x + y) = (exp 𝕂 x) * (exp 𝕂 y) :=
exp_add_of_mem_ball ((exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)
  ((exp_series_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)

/-- A version of `exp_sum_of_commute` for a commutative Banach-algebra. -/
lemma exp_sum {ι} (s : finset ι) (f : ι → 𝔸) :
  exp 𝕂 (∑ i in s, f i) = ∏ i in s, exp 𝕂 (f i) :=
begin
  rw [exp_sum_of_commute, finset.noncomm_prod_eq_prod],
  exact λ i hi j hj _, commute.all _ _,
end

end comm_algebra

end is_R_or_C

end normed

section scalar_tower

variables (𝕂 𝕂' 𝔸 : Type*) [field 𝕂] [field 𝕂'] [ring 𝔸] [algebra 𝕂 𝔸] [algebra 𝕂' 𝔸]
  [topological_space 𝔸] [topological_ring 𝔸]

/-- If a normed ring `𝔸` is a normed algebra over two fields, then they define the same
`exp_series` on `𝔸`. -/
lemma exp_series_eq_exp_series (n : ℕ) (x : 𝔸) :
  (exp_series 𝕂 𝔸 n (λ _, x)) = (exp_series 𝕂' 𝔸 n (λ _, x)) :=
by rw [exp_series_apply_eq, exp_series_apply_eq, inv_nat_cast_smul_eq 𝕂 𝕂']

/-- If a normed ring `𝔸` is a normed algebra over two fields, then they define the same
exponential function on `𝔸`. -/
lemma exp_eq_exp : (exp 𝕂 : 𝔸 → 𝔸) = exp 𝕂' :=
begin
  ext,
  rw [exp, exp],
  refine tsum_congr (λ n, _),
  rw exp_series_eq_exp_series 𝕂 𝕂' 𝔸 n x
end

lemma exp_ℝ_ℂ_eq_exp_ℂ_ℂ : (exp ℝ : ℂ → ℂ) = exp ℂ :=
exp_eq_exp ℝ ℂ ℂ

/-- A version of `complex.of_real_exp` for `exp` instead of `complex.exp` -/
@[simp, norm_cast]
lemma of_real_exp_ℝ_ℝ (r : ℝ) : ↑(exp ℝ r) = exp ℂ (r : ℂ) :=
(map_exp ℝ (algebra_map ℝ ℂ) (continuous_algebra_map _ _) r).trans
  (congr_fun exp_ℝ_ℂ_eq_exp_ℂ_ℂ _)

end scalar_tower


/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro

! This file was ported from Lean 3 source module data.fintype.quotient
! leanprover-community/mathlib commit d78597269638367c3863d40d45108f52207e03cf
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Fintype.Basic

/-!
# Quotients of families indexed by a finite type

This file provides `quotient.fin_choice`, a mechanism to go from a finite family of quotients
to a quotient of finite families.

## Main definitions

* `quotient.fin_choice`

-/

/-- An auxiliary function for `quotient.fin_choice`.  Given a
collection of setoids indexed by a type `ι`, a (finite) list `l` of
indices, and a function that for each `i ∈ l` gives a term of the
corresponding quotient type, then there is a corresponding term in the
quotient of the product of the setoids indexed by `l`. -/
def Quotient.finChoiceAux {ι : Type _} [DecidableEq ι] {α : ι → Type _} [S : ∀ i, Setoid (α i)] :
    ∀ l : List ι, (∀ i ∈ l, Quotient (S i)) → @Quotient (∀ i ∈ l, α i) (by infer_instance)
  | [], f => ⟦fun i => False.elim⟧
  | i :: l, f =>
    by
    refine'
      Quotient.liftOn₂ (f i (List.mem_cons_self _ _))
        (Quotient.finChoiceAux l fun j h => f j (List.mem_cons_of_mem _ h)) _ _
    exact fun a l =>
      ⟦fun j h => if e : j = i then by rw [e] <;> exact a else l _ (h.resolve_left e)⟧
    refine' fun a₁ l₁ a₂ l₂ h₁ h₂ => Quotient.sound fun j h => _
    by_cases e : j = i <;> simp [e]
    · subst j
      exact h₁
    · exact h₂ _ _
#align quotient.fin_choice_aux Quotient.finChoiceAux

theorem Quotient.finChoiceAux_eq {ι : Type _} [DecidableEq ι] {α : ι → Type _}
    [S : ∀ i, Setoid (α i)] :
    ∀ (l : List ι) (f : ∀ i ∈ l, α i), (Quotient.finChoiceAux l fun i h => ⟦f i h⟧) = ⟦f⟧
  | [], f => Quotient.sound fun i h => h.elim
  | i :: l, f => by
    simp [Quotient.finChoiceAux, Quotient.finChoiceAux_eq l]
    refine' Quotient.sound fun j h => _
    by_cases e : j = i <;> simp [e]
    subst j; rfl
#align quotient.fin_choice_aux_eq Quotient.finChoiceAux_eq

/-- Given a collection of setoids indexed by a fintype `ι` and a
function that for each `i : ι` gives a term of the corresponding
quotient type, then there is corresponding term in the quotient of the
product of the setoids. -/
def Quotient.finChoice {ι : Type _} [DecidableEq ι] [Fintype ι] {α : ι → Type _}
    [S : ∀ i, Setoid (α i)] (f : ∀ i, Quotient (S i)) : @Quotient (∀ i, α i) (by infer_instance) :=
  Quotient.liftOn
    (@Quotient.recOn _ _ (fun l : Multiset ι => @Quotient (∀ i ∈ l, α i) (by infer_instance))
      Finset.univ.1 (fun l => Quotient.finChoiceAux l fun i _ => f i) fun a b h =>
      by
      have := fun a => Quotient.finChoiceAux_eq a fun i h => Quotient.out (f i)
      simp [Quotient.out_eq] at this
      simp [this]
      let g := fun a : Multiset ι => ⟦fun (i : ι) (h : i ∈ a) => Quotient.out (f i)⟧
      refine' eq_of_hEq ((eq_rec_hEq _ _).trans (_ : HEq (g a) (g b)))
      congr 1; exact Quotient.sound h)
    (fun f => ⟦fun i => f i (Finset.mem_univ _)⟧) fun a b h => Quotient.sound fun i => h _ _
#align quotient.fin_choice Quotient.finChoice

theorem Quotient.finChoice_eq {ι : Type _} [DecidableEq ι] [Fintype ι] {α : ι → Type _}
    [∀ i, Setoid (α i)] (f : ∀ i, α i) : (Quotient.finChoice fun i => ⟦f i⟧) = ⟦f⟧ :=
  by
  let q
  swap
  change Quotient.liftOn q _ _ = _
  have : q = ⟦fun i h => f i⟧ := by
    dsimp only [q]
    exact Quotient.inductionOn (@Finset.univ ι _).1 fun l => Quotient.finChoiceAux_eq _ _
  simp [this]
  exact Setoid.refl _
#align quotient.fin_choice_eq Quotient.finChoice_eq


/-
Copyright (c) 2020 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon, Patrick Massot, Eric Wieser
-/
import tactic.to_additive
import algebra.group.defs
import logic.unique
import tactic.congr
import tactic.simpa
import tactic.split_ifs
import data.sum.basic
import data.prod.basic

/-!
# Instances and theorems on pi types

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

This file provides basic definitions and notation instances for Pi types.

Instances of more sophisticated classes are defined in `pi.lean` files elsewhere.
-/

open function

universes u v₁ v₂ v₃
variable {I : Type u}     -- The indexing type
variables {α β γ : Type*}
-- The families of types already equipped with instances
variables {f : I → Type v₁} {g : I → Type v₂} {h : I → Type v₃}
variables (x y : Π i, f i) (i : I)

namespace pi

/-! `1`, `0`, `+`, `*`, `+ᵥ`, `•`, `^`, `-`, `⁻¹`, and `/` are defined pointwise. -/

@[to_additive] instance has_one [∀ i, has_one $ f i] :
  has_one (Π i : I, f i) :=
⟨λ _, 1⟩
@[simp, to_additive] lemma one_apply [∀ i, has_one $ f i] : (1 : Π i, f i) i = 1 := rfl

@[to_additive] lemma one_def [Π i, has_one $ f i] : (1 : Π i, f i) = λ i, 1 := rfl

@[simp, to_additive] lemma const_one [has_one β] : const α (1 : β) = 1 := rfl

@[simp, to_additive] lemma one_comp [has_one γ] (x : α → β) : (1 : β → γ) ∘ x = 1 := rfl

@[simp, to_additive] lemma comp_one [has_one β] (x : β → γ) : x ∘ 1 = const α (x 1) := rfl

@[to_additive]
instance has_mul [∀ i, has_mul $ f i] :
  has_mul (Π i : I, f i) :=
⟨λ f g i, f i * g i⟩
@[simp, to_additive] lemma mul_apply [∀ i, has_mul $ f i] : (x * y) i = x i * y i := rfl

@[to_additive] lemma mul_def [Π i, has_mul $ f i] : x * y = λ i, x i * y i := rfl

@[simp, to_additive] lemma const_mul [has_mul β] (a b : β) :
  const α a * const α b = const α (a * b) := rfl

@[to_additive] lemma mul_comp [has_mul γ] (x y : β → γ) (z : α → β) :
  (x * y) ∘ z = x ∘ z * y ∘ z := rfl

@[to_additive pi.has_vadd] instance has_smul [Π i, has_smul α $ f i] : has_smul α (Π i : I, f i) :=
⟨λ s x, λ i, s • (x i)⟩

@[simp, to_additive] lemma smul_apply [Π i, has_smul α $ f i] (s : α) (x : Π i, f i) (i : I) :
  (s • x) i = s • x i := rfl

@[to_additive] lemma smul_def [Π i, has_smul α $ f i] (s : α) (x : Π i, f i) :
  s • x = λ i, s • x i := rfl

@[simp, to_additive]
lemma smul_const [has_smul α β] (a : α) (b : β) : a • const I b = const I (a • b) := rfl

@[to_additive]
lemma smul_comp [has_smul α γ] (a : α) (x : β → γ) (y : I → β) : (a • x) ∘ y = a • (x ∘ y) := rfl

@[to_additive pi.has_smul]
instance has_pow [Π i, has_pow (f i) β] : has_pow (Π i, f i) β :=
⟨λ x b i, (x i) ^ b⟩

@[simp, to_additive pi.smul_apply, to_additive_reorder 5]
lemma pow_apply [Π i, has_pow (f i) β] (x : Π i, f i) (b : β) (i : I) : (x ^ b) i = (x i) ^ b := rfl

@[to_additive pi.smul_def, to_additive_reorder 5]
lemma pow_def [Π i, has_pow (f i) β] (x : Π i, f i) (b : β) : x ^ b = λ i, (x i) ^ b := rfl

-- `to_additive` generates bad output if we take `has_pow α β`.
@[simp, to_additive smul_const, to_additive_reorder 5]
lemma const_pow [has_pow β α] (b : β) (a : α) : const I b ^ a = const I (b ^ a) := rfl

@[to_additive smul_comp, to_additive_reorder 6]
lemma pow_comp [has_pow γ α] (x : β → γ) (a : α) (y : I → β) : (x ^ a) ∘ y = (x ∘ y) ^ a := rfl

@[simp] lemma bit0_apply [Π i, has_add $ f i] : (bit0 x) i = bit0 (x i) := rfl

@[simp] lemma bit1_apply [Π i, has_add $ f i] [Π i, has_one $ f i] : (bit1 x) i = bit1 (x i) := rfl

@[to_additive] instance has_inv [∀ i, has_inv $ f i] :
  has_inv (Π i : I, f i) :=
  ⟨λ f i, (f i)⁻¹⟩
@[simp, to_additive] lemma inv_apply [∀ i, has_inv $ f i] : x⁻¹ i = (x i)⁻¹ := rfl
@[to_additive] lemma inv_def [Π i, has_inv $ f i] : x⁻¹ = λ i, (x i)⁻¹ := rfl

@[to_additive] lemma const_inv [has_inv β] (a : β) : (const α a)⁻¹ = const α a⁻¹ := rfl

@[to_additive] lemma inv_comp [has_inv γ] (x : β → γ) (y : α → β) : x⁻¹ ∘ y = (x ∘ y)⁻¹ := rfl

@[to_additive] instance has_div [Π i, has_div $ f i] :
  has_div (Π i : I, f i) :=
⟨λ f g i, f i / g i⟩
@[simp, to_additive] lemma div_apply [Π i, has_div $ f i] : (x / y) i = x i / y i := rfl
@[to_additive] lemma div_def [Π i, has_div $ f i] : x / y = λ i, x i / y i := rfl

@[to_additive] lemma div_comp [has_div γ] (x y : β → γ) (z : α → β) :
  (x / y) ∘ z = x ∘ z / y ∘ z := rfl

@[simp, to_additive] lemma const_div [has_div β] (a b : β) :
  const α a / const α b = const α (a / b) := rfl

section

variables [decidable_eq I]
variables [Π i, has_one (f i)] [Π i, has_one (g i)] [Π i, has_one (h i)]

/-- The function supported at `i`, with value `x` there, and `1` elsewhere. -/
@[to_additive pi.single "The function supported at `i`, with value `x` there, and `0` elsewhere." ]
def mul_single (i : I) (x : f i) : Π i, f i :=
function.update 1 i x

@[simp, to_additive]
lemma mul_single_eq_same (i : I) (x : f i) : mul_single i x i = x :=
function.update_same i x _

@[simp, to_additive]
lemma mul_single_eq_of_ne {i i' : I} (h : i' ≠ i) (x : f i) : mul_single i x i' = 1 :=
function.update_noteq h x _

/-- Abbreviation for `mul_single_eq_of_ne h.symm`, for ease of use by `simp`. -/
@[simp, to_additive "Abbreviation for `single_eq_of_ne h.symm`, for ease of
use by `simp`."]
lemma mul_single_eq_of_ne' {i i' : I} (h : i ≠ i') (x : f i) : mul_single i x i' = 1 :=
mul_single_eq_of_ne h.symm x

@[simp, to_additive]
lemma mul_single_one (i : I) : mul_single i (1 : f i) = 1 :=
function.update_eq_self _ _

/-- On non-dependent functions, `pi.mul_single` can be expressed as an `ite` -/
@[to_additive "On non-dependent functions, `pi.single` can be expressed as an `ite`"]
lemma mul_single_apply {β : Sort*} [has_one β] (i : I) (x : β) (i' : I) :
  mul_single i x i' = if i' = i then x else 1 :=
function.update_apply 1 i x i'

/-- On non-dependent functions, `pi.mul_single` is symmetric in the two indices. -/
@[to_additive "On non-dependent functions, `pi.single` is symmetric in the two
indices."]
lemma mul_single_comm {β : Sort*} [has_one β] (i : I) (x : β) (i' : I) :
  mul_single i x i' = mul_single i' x i :=
by simp [mul_single_apply, eq_comm]

@[to_additive]
lemma apply_mul_single (f' : Π i, f i → g i) (hf' : ∀ i, f' i 1 = 1) (i : I) (x : f i) (j : I):
  f' j (mul_single i x j) = mul_single i (f' i x) j :=
by simpa only [pi.one_apply, hf', mul_single] using function.apply_update f' 1 i x j

@[to_additive apply_single₂]
lemma apply_mul_single₂ (f' : Π i, f i → g i → h i) (hf' : ∀ i, f' i 1 1 = 1)
  (i : I) (x : f i) (y : g i) (j : I):
  f' j (mul_single i x j) (mul_single i y j) = mul_single i (f' i x y) j :=
begin
  by_cases h : j = i,
  { subst h, simp only [mul_single_eq_same] },
  { simp only [mul_single_eq_of_ne h, hf'] },
end

@[to_additive]

@[to_additive]
lemma mul_single_op₂ {g₁ g₂ : I → Type*} [Π i, has_one (g₁ i)] [Π i, has_one (g₂ i)]
  (op : Π i, g₁ i → g₂ i → f i) (h : ∀ i, op i 1 1 = 1) (i : I) (x₁ : g₁ i) (x₂ : g₂ i) :
  mul_single i (op i x₁ x₂) = λ j, op j (mul_single i x₁ j) (mul_single i x₂ j) :=
eq.symm $ funext $ apply_mul_single₂ op h i x₁ x₂

variables (f)

@[to_additive]
lemma mul_single_injective (i : I) : function.injective (mul_single i : f i → Π i, f i) :=
function.update_injective _ i

@[simp, to_additive]
lemma mul_single_inj (i : I) {x y : f i} : mul_single i x = mul_single i y ↔ x = y :=
(pi.mul_single_injective _ _).eq_iff

end

/-- The mapping into a product type built from maps into each component. -/
@[simp] protected def prod (f' : Π i, f i) (g' : Π i, g i) (i : I) : f i × g i := (f' i, g' i)

@[simp] lemma prod_fst_snd : pi.prod (prod.fst : α × β → α) (prod.snd : α × β → β) = id :=
funext $ λ _, prod.mk.eta

@[simp] lemma prod_snd_fst : pi.prod (prod.snd : α × β → β) (prod.fst : α × β → α) = prod.swap :=
rfl

end pi

namespace function

section extend
@[to_additive]
lemma extend_one [has_one γ] (f : α → β) :
  function.extend f (1 : α → γ) (1 : β → γ) = 1 :=
funext $ λ _, by apply if_t_t _ _

@[to_additive]
lemma extend_mul [has_mul γ] (f : α → β) (g₁ g₂ : α → γ) (e₁ e₂ : β → γ) :
  function.extend f (g₁ * g₂) (e₁ * e₂) = function.extend f g₁ e₁ * function.extend f g₂ e₂ :=
funext $ λ _, by convert (apply_dite2 (*) _ _ _ _ _).symm

@[to_additive]
lemma extend_inv [has_inv γ] (f : α → β) (g : α → γ) (e : β → γ) :
  function.extend f (g⁻¹) (e⁻¹) = (function.extend f g e)⁻¹ :=
funext $ λ _, by convert (apply_dite has_inv.inv _ _ _).symm

@[to_additive]
lemma extend_div [has_div γ] (f : α → β) (g₁ g₂ : α → γ) (e₁ e₂ : β → γ) :
  function.extend f (g₁ / g₂) (e₁ / e₂) = function.extend f g₁ e₁ / function.extend f g₂ e₂ :=
funext $ λ _, by convert (apply_dite2 (/) _ _ _ _ _).symm

end extend

lemma surjective_pi_map {F : Π i, f i → g i} (hF : ∀ i, surjective (F i)) :
  surjective (λ x : Π i, f i, λ i, F i (x i)) :=
λ y, ⟨λ i, (hF i (y i)).some, funext $ λ i, (hF i (y i)).some_spec⟩

lemma injective_pi_map {F : Π i, f i → g i} (hF : ∀ i, injective (F i)) :
  injective (λ x : Π i, f i, λ i, F i (x i)) :=
λ x y h, funext $ λ i, hF i $ (congr_fun h i : _)

lemma bijective_pi_map {F : Π i, f i → g i} (hF : ∀ i, bijective (F i)) :
  bijective (λ x : Π i, f i, λ i, F i (x i)) :=
⟨injective_pi_map (λ i, (hF i).injective), surjective_pi_map (λ i, (hF i).surjective)⟩

end function

/-- If the one function is surjective, the codomain is trivial. -/
@[to_additive "If the zero function is surjective, the codomain is trivial."]
def unique_of_surjective_one (α : Type*) {β : Type*} [has_one β]
  (h : function.surjective (1 : α → β)) : unique β :=
h.unique_of_surjective_const α (1 : β)

@[to_additive subsingleton.pi_single_eq]
lemma subsingleton.pi_mul_single_eq {α : Type*} [decidable_eq I] [subsingleton I] [has_one α]
  (i : I) (x : α) :
  pi.mul_single i x = λ _, x :=
funext $ λ j, by rw [subsingleton.elim j i, pi.mul_single_eq_same]

namespace sum
variables (a a' : α → γ) (b b' : β → γ)

@[simp, to_additive]
lemma elim_one_one [has_one γ] :
  sum.elim (1 : α → γ) (1 : β → γ) = 1 :=
sum.elim_const_const 1

@[simp, to_additive]
lemma elim_mul_single_one [decidable_eq α] [decidable_eq β] [has_one γ] (i : α) (c : γ) :
  sum.elim (pi.mul_single i c) (1 : β → γ) = pi.mul_single (sum.inl i) c :=
by simp only [pi.mul_single, sum.elim_update_left, elim_one_one]

@[simp, to_additive]
lemma elim_one_mul_single [decidable_eq α] [decidable_eq β] [has_one γ] (i : β) (c : γ) :
  sum.elim (1 : α → γ) (pi.mul_single i c) = pi.mul_single (sum.inr i) c :=
by simp only [pi.mul_single, sum.elim_update_right, elim_one_one]

@[to_additive]
lemma elim_inv_inv [has_inv γ] :
  sum.elim a⁻¹ b⁻¹ = (sum.elim a b)⁻¹ :=
(sum.comp_elim has_inv.inv a b).symm

@[to_additive]
lemma elim_mul_mul [has_mul γ] :
  sum.elim (a * a') (b * b') = sum.elim a b * sum.elim a' b' :=
by { ext x, cases x; refl }

@[to_additive]
lemma elim_div_div [has_div γ] :
  sum.elim (a / a') (b / b') = sum.elim a b / sum.elim a' b' :=
by { ext x, cases x; refl }

end sum


/-
Copyright (c) 2022 Ian Benway. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Ian Benway.
-/
import Lean

namespace Mathlib.Tactic
open Lean Elab Elab.Tactic Meta

syntax setArgsRest := ppSpace ident (" : " term)? " := " term (" with " "←"? ident)?

syntax (name := set) "set" "!"? setArgsRest : tactic

macro "set!" rest:setArgsRest : tactic => `(tactic|set ! $rest:setArgsRest)

/--
`set a := t with h` is a variant of `let a := t`. It adds the hypothesis `h : a = t` to
the local context and replaces `t` with `a` everywhere it can.

`set a := t with ← h` will add `h : t = a` instead.

`set! a := t with h` does not do any replacing.

```lean
example (x : Nat) (h : x + x - x = 3) : x + x - x = 3 := by
  set y := x with ← h2
  sorry
/-
x : Nat
y : Nat := x
h : y + y - y = 3
h2 : x = y
⊢ y + y - y = 3
-/
```

-/
elab_rules : tactic
| `(tactic| set $[!%$rw]? $a:ident $[: $ty:term]? := $val:term $[with $[←%$rev]? $h:ident]?) => do
  withMainContext do
    let (ty, val) ← match ty with
    | some ty =>
      let ty ← Term.elabType ty
      pure (ty, ← elabTermEnsuringType val ty)
    | none =>
      let val ← elabTerm val none
      pure (← inferType val, val)
    let fvar ← liftMetaTacticAux fun goal ↦ do
      let (fvar, goal) ← (← goal.define a.getId ty val).intro1P
      pure (fvar, [goal])
    withMainContext do
      Term.addTermInfo' (isBinder := true) a (mkFVar fvar)
  if rw.isNone then
    evalTactic (← `(tactic| try rewrite [(id rfl : $val = $a)] at *))
  match h, rev with
  | some h, some none =>
    evalTactic (← `(tactic| have $h : $a = $val := rfl))
  | some h, some (some _) =>
    evalTactic (← `(tactic| have $h : $val = $a := rfl))
  | _, _ => pure ()


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

! This file was ported from Lean 3 source module analysis.special_functions.integrals
! leanprover-community/mathlib commit 8c8c544bf24ced19b1e76c34bb3262bdae620f82
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.MeasureTheory.Integral.IntervalIntegral
import Mathbin.Analysis.SpecialFunctions.Trigonometric.ArctanDeriv

/-!
# Integration of specific interval integrals

This file contains proofs of the integrals of various specific functions. This includes:
* Integrals of simple functions, such as `id`, `pow`, `inv`, `exp`, `log`
* Integrals of some trigonometric functions, such as `sin`, `cos`, `1 / (1 + x^2)`
* The integral of `cos x ^ 2 - sin x ^ 2`
* Reduction formulae for the integrals of `sin x ^ n` and `cos x ^ n` for `n ≥ 2`
* The computation of `∫ x in 0..π, sin x ^ n` as a product for even and odd `n` (used in proving the
  Wallis product for pi)
* Integrals of the form `sin x ^ m * cos x ^ n`

With these lemmas, many simple integrals can be computed by `simp` or `norm_num`.
See `test/integration.lean` for specific examples.

This file also contains some facts about the interval integrability of specific functions.

This file is still being developed.

## Tags

integrate, integration, integrable, integrability
-/

open Real Nat Set Finset

open Real BigOperators Interval

variable {a b : ℝ} (n : ℕ)

namespace intervalIntegral

open MeasureTheory

variable {f : ℝ → ℝ} {μ ν : Measure ℝ} [IsLocallyFiniteMeasure μ] (c d : ℝ)

/-! ### Interval integrability -/

@[simp]
theorem intervalIntegrablePow : IntervalIntegrable (fun x => x ^ n) μ a b :=
  (continuous_pow n).IntervalIntegrable a b
#align interval_integral.interval_integrable_pow intervalIntegral.intervalIntegrablePow

theorem intervalIntegrableZpow {n : ℤ} (h : 0 ≤ n ∨ (0 : ℝ) ∉ [a, b]) :
    IntervalIntegrable (fun x => x ^ n) μ a b :=
  (continuousOn_id.zpow₀ n fun x hx => h.symm.imp (ne_of_mem_of_not_mem hx) id).IntervalIntegrable
#align interval_integral.interval_integrable_zpow intervalIntegral.intervalIntegrableZpow

/-- See `interval_integrable_rpow'` for a version with a weaker hypothesis on `r`, but assuming the
measure is volume. -/
theorem intervalIntegrableRpow {r : ℝ} (h : 0 ≤ r ∨ (0 : ℝ) ∉ [a, b]) :
    IntervalIntegrable (fun x => x ^ r) μ a b :=
  (continuousOn_id.rpow_const fun x hx =>
      h.symm.imp (ne_of_mem_of_not_mem hx) id).IntervalIntegrable
#align interval_integral.interval_integrable_rpow intervalIntegral.intervalIntegrableRpow

/-- See `interval_integrable_rpow` for a version applying to any locally finite measure, but with a
stronger hypothesis on `r`. -/
theorem intervalIntegrableRpow' {r : ℝ} (h : -1 < r) :
    IntervalIntegrable (fun x => x ^ r) volume a b :=
  by
  suffices ∀ c : ℝ, IntervalIntegrable (fun x => x ^ r) volume 0 c by
    exact IntervalIntegrable.trans (this a).symm (this b)
  have : ∀ c : ℝ, 0 ≤ c → IntervalIntegrable (fun x => x ^ r) volume 0 c :=
    by
    intro c hc
    rw [intervalIntegrable_iff, uIoc_of_le hc]
    have hderiv : ∀ x ∈ Ioo 0 c, HasDerivAt (fun x : ℝ => x ^ (r + 1) / (r + 1)) (x ^ r) x :=
      by
      intro x hx
      convert(Real.hasDerivAt_rpow_const (Or.inl hx.1.ne')).div_const (r + 1)
      field_simp [(by linarith : r + 1 ≠ 0)]
      ring
    apply integrable_on_deriv_of_nonneg hc _ hderiv
    · intro x hx
      apply rpow_nonneg_of_nonneg hx.1.le
    · refine' (continuous_on_id.rpow_const _).div_const _
      intro x hx
      right
      linarith
  intro c
  rcases le_total 0 c with (hc | hc)
  · exact this c hc
  · rw [IntervalIntegrable.iff_comp_neg, neg_zero]
    have m := (this (-c) (by linarith)).smul (cos (r * π))
    rw [intervalIntegrable_iff] at m⊢
    refine' m.congr_fun _ measurableSet_Ioc
    intro x hx
    rw [uIoc_of_le (by linarith : 0 ≤ -c)] at hx
    simp only [Pi.smul_apply, Algebra.id.smul_eq_mul, log_neg_eq_log, mul_comm,
      rpow_def_of_pos hx.1, rpow_def_of_neg (by linarith [hx.1] : -x < 0)]
#align interval_integral.interval_integrable_rpow' intervalIntegral.intervalIntegrableRpow'

/-- See `interval_integrable_cpow'` for a version with a weaker hypothesis on `r`, but assuming the
measure is volume. -/
theorem intervalIntegrableCpow {r : ℂ} (h : 0 ≤ r.re ∨ (0 : ℝ) ∉ [a, b]) :
    IntervalIntegrable (fun x : ℝ => (x : ℂ) ^ r) μ a b :=
  by
  by_cases h2 : (0 : ℝ) ∉ [a, b]
  · -- Easy case #1: 0 ∉ [a, b] -- use continuity.
    refine' (ContinuousAt.continuousOn fun x hx => _).IntervalIntegrable
    exact Complex.continuousAt_of_real_cpow_const _ _ (Or.inr <| ne_of_mem_of_not_mem hx h2)
  rw [eq_false h2, or_false_iff] at h
  rcases lt_or_eq_of_le h with (h' | h')
  ·-- Easy case #2: 0 < re r -- again use continuity
    exact (Complex.continuous_of_real_cpow_const h').IntervalIntegrable _ _
  -- Now the hard case: re r = 0 and 0 is in the interval.
  refine' (IntervalIntegrable.intervalIntegrable_norm_iff _).mp _
  · refine' (measurable_of_continuousOn_compl_singleton (0 : ℝ) _).AeStronglyMeasurable
    exact
      ContinuousAt.continuousOn fun x hx => Complex.continuousAt_of_real_cpow_const x r (Or.inr hx)
  -- reduce to case of integral over `[0, c]`
  suffices : ∀ c : ℝ, IntervalIntegrable (fun x : ℝ => ‖↑x ^ r‖) μ 0 c
  exact (this a).symm.trans (this b)
  intro c
  rcases le_or_lt 0 c with (hc | hc)
  · -- case `0 ≤ c`: integrand is identically 1
    have : IntervalIntegrable (fun x => 1 : ℝ → ℝ) μ 0 c := intervalIntegrableConst
    rw [intervalIntegrable_iff_integrable_Ioc_of_le hc] at this⊢
    refine' integrable_on.congr_fun this (fun x hx => _) measurableSet_Ioc
    dsimp only
    rw [Complex.norm_eq_abs, Complex.abs_cpow_eq_rpow_re_of_pos hx.1, ← h', rpow_zero]
  · -- case `c < 0`: integrand is identically constant, *except* at `x = 0` if `r ≠ 0`.
    apply IntervalIntegrable.symm
    rw [intervalIntegrable_iff_integrable_Ioc_of_le hc.le]
    have : Ioc c 0 = Ioo c 0 ∪ {(0 : ℝ)} :=
      by
      rw [← Ioo_union_Icc_eq_Ioc hc (le_refl 0), ← Icc_def]
      simp_rw [← le_antisymm_iff, set_of_eq_eq_singleton']
    rw [this, integrable_on_union, and_comm]
    constructor
    · refine' integrable_on_singleton_iff.mpr (Or.inr _)
      exact
        is_finite_measure_on_compacts_of_is_locally_finite_measure.lt_top_of_is_compact
          isCompact_singleton
    · have : ∀ x : ℝ, x ∈ Ioo c 0 → ‖Complex.exp (↑π * Complex.I * r)‖ = ‖(x : ℂ) ^ r‖ :=
        by
        intro x hx
        rw [Complex.of_real_cpow_of_nonpos hx.2.le, norm_mul, ← Complex.ofReal_neg,
          Complex.norm_eq_abs (_ ^ _), Complex.abs_cpow_eq_rpow_re_of_pos (neg_pos.mpr hx.2), ← h',
          rpow_zero, one_mul]
      refine' integrable_on.congr_fun _ this measurableSet_Ioo
      rw [integrable_on_const]
      refine' Or.inr ((measure_mono Set.Ioo_subset_Icc_self).trans_lt _)
      exact
        is_finite_measure_on_compacts_of_is_locally_finite_measure.lt_top_of_is_compact
          is_compact_Icc
#align interval_integral.interval_integrable_cpow intervalIntegral.intervalIntegrableCpow

/-- See `interval_integrable_cpow` for a version applying to any locally finite measure, but with a
stronger hypothesis on `r`. -/
theorem intervalIntegrableCpow' {r : ℂ} (h : -1 < r.re) :
    IntervalIntegrable (fun x : ℝ => (x : ℂ) ^ r) volume a b :=
  by
  suffices ∀ c : ℝ, IntervalIntegrable (fun x => (x : ℂ) ^ r) volume 0 c by
    exact IntervalIntegrable.trans (this a).symm (this b)
  have : ∀ c : ℝ, 0 ≤ c → IntervalIntegrable (fun x => (x : ℂ) ^ r) volume 0 c :=
    by
    intro c hc
    rw [← IntervalIntegrable.intervalIntegrable_norm_iff]
    · rw [intervalIntegrable_iff]
      apply integrable_on.congr_fun
      · rw [← intervalIntegrable_iff]
        exact intervalIntegral.intervalIntegrableRpow' h
      · intro x hx
        rw [uIoc_of_le hc] at hx
        dsimp only
        rw [Complex.norm_eq_abs, Complex.abs_cpow_eq_rpow_re_of_pos hx.1]
      · exact measurableSet_uIoc
    · refine' ContinuousOn.aeStronglyMeasurable _ measurableSet_uIoc
      refine' ContinuousAt.continuousOn fun x hx => _
      rw [uIoc_of_le hc] at hx
      refine' (continuousAt_cpow_const (Or.inl _)).comp complex.continuous_of_real.continuous_at
      rw [Complex.ofReal_re]
      exact hx.1
  intro c
  rcases le_total 0 c with (hc | hc)
  · exact this c hc
  · rw [IntervalIntegrable.iff_comp_neg, neg_zero]
    have m := (this (-c) (by linarith)).const_mul (Complex.exp (π * Complex.I * r))
    rw [intervalIntegrable_iff, uIoc_of_le (by linarith : 0 ≤ -c)] at m⊢
    refine' m.congr_fun (fun x hx => _) measurableSet_Ioc
    dsimp only
    have : -x ≤ 0 := by linarith [hx.1]
    rw [Complex.of_real_cpow_of_nonpos this, mul_comm]
    simp
#align interval_integral.interval_integrable_cpow' intervalIntegral.intervalIntegrableCpow'

@[simp]
theorem intervalIntegrableId : IntervalIntegrable (fun x => x) μ a b :=
  continuous_id.IntervalIntegrable a b
#align interval_integral.interval_integrable_id intervalIntegral.intervalIntegrableId

@[simp]
theorem intervalIntegrableConst : IntervalIntegrable (fun x => c) μ a b :=
  continuous_const.IntervalIntegrable a b
#align interval_integral.interval_integrable_const intervalIntegral.intervalIntegrableConst

theorem intervalIntegrableOneDiv (h : ∀ x : ℝ, x ∈ [a, b] → f x ≠ 0) (hf : ContinuousOn f [a, b]) :
    IntervalIntegrable (fun x => 1 / f x) μ a b :=
  (continuousOn_const.div hf h).IntervalIntegrable
#align interval_integral.interval_integrable_one_div intervalIntegral.intervalIntegrableOneDiv

@[simp]
theorem intervalIntegrableInv (h : ∀ x : ℝ, x ∈ [a, b] → f x ≠ 0) (hf : ContinuousOn f [a, b]) :
    IntervalIntegrable (fun x => (f x)⁻¹) μ a b := by
  simpa only [one_div] using interval_integrable_one_div h hf
#align interval_integral.interval_integrable_inv intervalIntegral.intervalIntegrableInv

@[simp]
theorem intervalIntegrableExp : IntervalIntegrable exp μ a b :=
  continuous_exp.IntervalIntegrable a b
#align interval_integral.interval_integrable_exp intervalIntegral.intervalIntegrableExp

@[simp]
theorem IntervalIntegrable.log (hf : ContinuousOn f [a, b]) (h : ∀ x : ℝ, x ∈ [a, b] → f x ≠ 0) :
    IntervalIntegrable (fun x => log (f x)) μ a b :=
  (ContinuousOn.log hf h).IntervalIntegrable
#align interval_integrable.log IntervalIntegrable.log

@[simp]
theorem intervalIntegrableLog (h : (0 : ℝ) ∉ [a, b]) : IntervalIntegrable log μ a b :=
  IntervalIntegrable.log continuousOn_id fun x hx => ne_of_mem_of_not_mem hx h
#align interval_integral.interval_integrable_log intervalIntegral.intervalIntegrableLog

@[simp]
theorem intervalIntegrableSin : IntervalIntegrable sin μ a b :=
  continuous_sin.IntervalIntegrable a b
#align interval_integral.interval_integrable_sin intervalIntegral.intervalIntegrableSin

@[simp]
theorem intervalIntegrableCos : IntervalIntegrable cos μ a b :=
  continuous_cos.IntervalIntegrable a b
#align interval_integral.interval_integrable_cos intervalIntegral.intervalIntegrableCos

theorem intervalIntegrableOneDivOneAddSq :
    IntervalIntegrable (fun x : ℝ => 1 / (1 + x ^ 2)) μ a b :=
  by
  refine' (continuous_const.div _ fun x => _).IntervalIntegrable a b
  · continuity
  · nlinarith
#align interval_integral.interval_integrable_one_div_one_add_sq intervalIntegral.intervalIntegrableOneDivOneAddSq

@[simp]
theorem intervalIntegrableInvOneAddSq : IntervalIntegrable (fun x : ℝ => (1 + x ^ 2)⁻¹) μ a b := by
  simpa only [one_div] using interval_integrable_one_div_one_add_sq
#align interval_integral.interval_integrable_inv_one_add_sq intervalIntegral.intervalIntegrableInvOneAddSq

/-! ### Integrals of the form `c * ∫ x in a..b, f (c * x + d)` -/

@[simp]
theorem mul_integral_comp_mul_right : (c * ∫ x in a..b, f (x * c)) = ∫ x in a * c..b * c, f x :=
  smul_integral_comp_mul_right f c
#align interval_integral.mul_integral_comp_mul_right intervalIntegral.mul_integral_comp_mul_right

@[simp]
theorem mul_integral_comp_mul_left : (c * ∫ x in a..b, f (c * x)) = ∫ x in c * a..c * b, f x :=
  smul_integral_comp_mul_left f c
#align interval_integral.mul_integral_comp_mul_left intervalIntegral.mul_integral_comp_mul_left

@[simp]
theorem inv_mul_integral_comp_div : (c⁻¹ * ∫ x in a..b, f (x / c)) = ∫ x in a / c..b / c, f x :=
  inv_smul_integral_comp_div f c
#align interval_integral.inv_mul_integral_comp_div intervalIntegral.inv_mul_integral_comp_div

@[simp]
theorem mul_integral_comp_mul_add :
    (c * ∫ x in a..b, f (c * x + d)) = ∫ x in c * a + d..c * b + d, f x :=
  smul_integral_comp_mul_add f c d
#align interval_integral.mul_integral_comp_mul_add intervalIntegral.mul_integral_comp_mul_add

@[simp]
theorem mul_integral_comp_add_mul :
    (c * ∫ x in a..b, f (d + c * x)) = ∫ x in d + c * a..d + c * b, f x :=
  smul_integral_comp_add_mul f c d
#align interval_integral.mul_integral_comp_add_mul intervalIntegral.mul_integral_comp_add_mul

@[simp]
theorem inv_mul_integral_comp_div_add :
    (c⁻¹ * ∫ x in a..b, f (x / c + d)) = ∫ x in a / c + d..b / c + d, f x :=
  inv_smul_integral_comp_div_add f c d
#align interval_integral.inv_mul_integral_comp_div_add intervalIntegral.inv_mul_integral_comp_div_add

@[simp]
theorem inv_mul_integral_comp_add_div :
    (c⁻¹ * ∫ x in a..b, f (d + x / c)) = ∫ x in d + a / c..d + b / c, f x :=
  inv_smul_integral_comp_add_div f c d
#align interval_integral.inv_mul_integral_comp_add_div intervalIntegral.inv_mul_integral_comp_add_div

@[simp]
theorem mul_integral_comp_mul_sub :
    (c * ∫ x in a..b, f (c * x - d)) = ∫ x in c * a - d..c * b - d, f x :=
  smul_integral_comp_mul_sub f c d
#align interval_integral.mul_integral_comp_mul_sub intervalIntegral.mul_integral_comp_mul_sub

@[simp]
theorem mul_integral_comp_sub_mul :
    (c * ∫ x in a..b, f (d - c * x)) = ∫ x in d - c * b..d - c * a, f x :=
  smul_integral_comp_sub_mul f c d
#align interval_integral.mul_integral_comp_sub_mul intervalIntegral.mul_integral_comp_sub_mul

@[simp]
theorem inv_mul_integral_comp_div_sub :
    (c⁻¹ * ∫ x in a..b, f (x / c - d)) = ∫ x in a / c - d..b / c - d, f x :=
  inv_smul_integral_comp_div_sub f c d
#align interval_integral.inv_mul_integral_comp_div_sub intervalIntegral.inv_mul_integral_comp_div_sub

@[simp]
theorem inv_mul_integral_comp_sub_div :
    (c⁻¹ * ∫ x in a..b, f (d - x / c)) = ∫ x in d - b / c..d - a / c, f x :=
  inv_smul_integral_comp_sub_div f c d
#align interval_integral.inv_mul_integral_comp_sub_div intervalIntegral.inv_mul_integral_comp_sub_div

end intervalIntegral

open intervalIntegral

/-! ### Integrals of simple functions -/

theorem integral_cpow {r : ℂ} (h : -1 < r.re ∨ r ≠ -1 ∧ (0 : ℝ) ∉ [a, b]) :
    (∫ x : ℝ in a..b, (x : ℂ) ^ r) = (b ^ (r + 1) - a ^ (r + 1)) / (r + 1) :=
  by
  rw [sub_div]
  have hr : r + 1 ≠ 0 := by
    cases h
    · apply_fun Complex.re
      rw [Complex.add_re, Complex.one_re, Complex.zero_re, Ne.def, add_eq_zero_iff_eq_neg]
      exact h.ne'
    · rw [Ne.def, ← add_eq_zero_iff_eq_neg] at h
      exact h.1
  by_cases hab : (0 : ℝ) ∉ [a, b]
  · refine' integral_eq_sub_of_has_deriv_at (fun x hx => _) (interval_integrable_cpow <| Or.inr hab)
    refine' hasDerivAt_of_real_cpow (ne_of_mem_of_not_mem hx hab) _
    contrapose! hr
    rwa [add_eq_zero_iff_eq_neg]
  replace h : -1 < r.re
  · tauto
  suffices ∀ c : ℝ, (∫ x : ℝ in 0 ..c, (x : ℂ) ^ r) = c ^ (r + 1) / (r + 1) - 0 ^ (r + 1) / (r + 1)
    by
    rw [←
      integral_add_adjacent_intervals (@interval_integrable_cpow' a 0 r h)
        (@interval_integrable_cpow' 0 b r h),
      integral_symm, this a, this b, Complex.zero_cpow hr]
    ring
  intro c
  apply integral_eq_sub_of_has_deriv_right
  · refine' ((Complex.continuous_of_real_cpow_const _).div_const _).ContinuousOn
    rwa [Complex.add_re, Complex.one_re, ← neg_lt_iff_pos_add]
  · refine' fun x hx => (hasDerivAt_of_real_cpow _ _).HasDerivWithinAt
    · rcases le_total c 0 with (hc | hc)
      · rw [max_eq_left hc] at hx
        exact hx.2.Ne
      · rw [min_eq_left hc] at hx
        exact hx.1.ne'
    · contrapose! hr
      rw [hr]
      ring
  · exact interval_integrable_cpow' h
#align integral_cpow integral_cpow

theorem integral_rpow {r : ℝ} (h : -1 < r ∨ r ≠ -1 ∧ (0 : ℝ) ∉ [a, b]) :
    (∫ x in a..b, x ^ r) = (b ^ (r + 1) - a ^ (r + 1)) / (r + 1) :=
  by
  have h' : -1 < (r : ℂ).re ∨ (r : ℂ) ≠ -1 ∧ (0 : ℝ) ∉ [a, b] :=
    by
    cases h
    · left
      rwa [Complex.ofReal_re]
    · right
      rwa [← Complex.ofReal_one, ← Complex.ofReal_neg, Ne.def, Complex.ofReal_inj]
  have :
    (∫ x in a..b, (x : ℂ) ^ (r : ℂ)) = ((b : ℂ) ^ (r + 1 : ℂ) - (a : ℂ) ^ (r + 1 : ℂ)) / (r + 1) :=
    integral_cpow h'
  apply_fun Complex.re  at this
  convert this
  · simp_rw [interval_integral_eq_integral_uIoc, Complex.real_smul, Complex.ofReal_mul_re]
    · change Complex.re with IsROrC.re
      rw [← integral_re]
      rfl
      refine' interval_integrable_iff.mp _
      cases h'
      · exact interval_integrable_cpow' h'
      · exact interval_integrable_cpow (Or.inr h'.2)
  · rw [(by push_cast : (r : ℂ) + 1 = ((r + 1 : ℝ) : ℂ))]
    simp_rw [div_eq_inv_mul, ← Complex.ofReal_inv, Complex.ofReal_mul_re, Complex.sub_re]
    rfl
#align integral_rpow integral_rpow

theorem integral_zpow {n : ℤ} (h : 0 ≤ n ∨ n ≠ -1 ∧ (0 : ℝ) ∉ [a, b]) :
    (∫ x in a..b, x ^ n) = (b ^ (n + 1) - a ^ (n + 1)) / (n + 1) :=
  by
  replace h : -1 < (n : ℝ) ∨ (n : ℝ) ≠ -1 ∧ (0 : ℝ) ∉ [a, b]; · exact_mod_cast h
  exact_mod_cast integral_rpow h
#align integral_zpow integral_zpow

@[simp]
theorem integral_pow : (∫ x in a..b, x ^ n) = (b ^ (n + 1) - a ^ (n + 1)) / (n + 1) := by
  simpa only [← Int.ofNat_succ, zpow_ofNat] using integral_zpow (Or.inl (Int.coe_nat_nonneg n))
#align integral_pow integral_pow

/-- Integral of `|x - a| ^ n` over `Ι a b`. This integral appears in the proof of the
Picard-Lindelöf/Cauchy-Lipschitz theorem. -/
theorem integral_pow_abs_sub_uIoc : (∫ x in Ι a b, |x - a| ^ n) = |b - a| ^ (n + 1) / (n + 1) :=
  by
  cases' le_or_lt a b with hab hab
  ·
    calc
      (∫ x in Ι a b, |x - a| ^ n) = ∫ x in a..b, |x - a| ^ n := by
        rw [uIoc_of_le hab, ← integral_of_le hab]
      _ = ∫ x in 0 ..b - a, x ^ n :=
        by
        simp only [integral_comp_sub_right fun x => |x| ^ n, sub_self]
        refine' integral_congr fun x hx => congr_arg₂ Pow.pow (abs_of_nonneg <| _) rfl
        rw [uIcc_of_le (sub_nonneg.2 hab)] at hx
        exact hx.1
      _ = |b - a| ^ (n + 1) / (n + 1) := by simp [abs_of_nonneg (sub_nonneg.2 hab)]

  ·
    calc
      (∫ x in Ι a b, |x - a| ^ n) = ∫ x in b..a, |x - a| ^ n := by
        rw [uIoc_of_lt hab, ← integral_of_le hab.le]
      _ = ∫ x in b - a..0, (-x) ^ n :=
        by
        simp only [integral_comp_sub_right fun x => |x| ^ n, sub_self]
        refine' integral_congr fun x hx => congr_arg₂ Pow.pow (abs_of_nonpos <| _) rfl
        rw [uIcc_of_le (sub_nonpos.2 hab.le)] at hx
        exact hx.2
      _ = |b - a| ^ (n + 1) / (n + 1) := by
        simp [integral_comp_neg fun x => x ^ n, abs_of_neg (sub_neg.2 hab)]

#align integral_pow_abs_sub_uIoc integral_pow_abs_sub_uIoc

@[simp]
theorem integral_id : (∫ x in a..b, x) = (b ^ 2 - a ^ 2) / 2 := by simpa using integral_pow 1
#align integral_id integral_id

@[simp]
theorem integral_one : (∫ x in a..b, (1 : ℝ)) = b - a := by
  simp only [mul_one, smul_eq_mul, integral_const]
#align integral_one integral_one

theorem integral_const_on_unit_interval : (∫ x in a..a + 1, b) = b := by simp
#align integral_const_on_unit_interval integral_const_on_unit_interval

@[simp]
theorem integral_inv (h : (0 : ℝ) ∉ [a, b]) : (∫ x in a..b, x⁻¹) = log (b / a) :=
  by
  have h' := fun x hx => ne_of_mem_of_not_mem hx h
  rw [integral_deriv_eq_sub' _ deriv_log' (fun x hx => differentiable_at_log (h' x hx))
      (continuous_on_inv₀.mono <| subset_compl_singleton_iff.mpr h),
    log_div (h' b right_mem_uIcc) (h' a left_mem_uIcc)]
#align integral_inv integral_inv

@[simp]
theorem integral_inv_of_pos (ha : 0 < a) (hb : 0 < b) : (∫ x in a..b, x⁻¹) = log (b / a) :=
  integral_inv <| not_mem_uIcc_of_lt ha hb
#align integral_inv_of_pos integral_inv_of_pos

@[simp]
theorem integral_inv_of_neg (ha : a < 0) (hb : b < 0) : (∫ x in a..b, x⁻¹) = log (b / a) :=
  integral_inv <| not_mem_uIcc_of_gt ha hb
#align integral_inv_of_neg integral_inv_of_neg

theorem integral_one_div (h : (0 : ℝ) ∉ [a, b]) : (∫ x : ℝ in a..b, 1 / x) = log (b / a) := by
  simp only [one_div, integral_inv h]
#align integral_one_div integral_one_div

theorem integral_one_div_of_pos (ha : 0 < a) (hb : 0 < b) :
    (∫ x : ℝ in a..b, 1 / x) = log (b / a) := by simp only [one_div, integral_inv_of_pos ha hb]
#align integral_one_div_of_pos integral_one_div_of_pos

theorem integral_one_div_of_neg (ha : a < 0) (hb : b < 0) :
    (∫ x : ℝ in a..b, 1 / x) = log (b / a) := by simp only [one_div, integral_inv_of_neg ha hb]
#align integral_one_div_of_neg integral_one_div_of_neg

@[simp]
theorem integral_exp : (∫ x in a..b, exp x) = exp b - exp a := by
  rw [integral_deriv_eq_sub'] <;> norm_num [continuous_on_exp]
#align integral_exp integral_exp

theorem integral_exp_mul_complex {c : ℂ} (hc : c ≠ 0) :
    (∫ x in a..b, Complex.exp (c * x)) = (Complex.exp (c * b) - Complex.exp (c * a)) / c :=
  by
  have D : ∀ x : ℝ, HasDerivAt (fun y : ℝ => Complex.exp (c * y) / c) (Complex.exp (c * x)) x :=
    by
    intro x
    conv =>
      congr
      skip
      rw [← mul_div_cancel (Complex.exp (c * x)) hc]
    convert((Complex.hasDerivAt_exp _).comp x _).div_const c using 1
    simpa only [mul_one] using ((hasDerivAt_id (x : ℂ)).const_mul _).comp_of_real
  rw [integral_deriv_eq_sub' _ (funext fun x => (D x).deriv) fun x hx => (D x).DifferentiableAt]
  · ring_nf
  · apply Continuous.continuousOn
    continuity
#align integral_exp_mul_complex integral_exp_mul_complex

@[simp]
theorem integral_log (h : (0 : ℝ) ∉ [a, b]) :
    (∫ x in a..b, log x) = b * log b - a * log a - b + a :=
  by
  obtain ⟨h', heq⟩ := fun x hx => ne_of_mem_of_not_mem hx h, fun x hx => mul_inv_cancel (h' x hx)
  convert integral_mul_deriv_eq_deriv_mul (fun x hx => has_deriv_at_log (h' x hx))
        (fun x hx => hasDerivAt_id x)
        (continuous_on_inv₀.mono <| subset_compl_singleton_iff.mpr h).IntervalIntegrable
        continuous_on_const.interval_integrable using
      1 <;>
    simp [integral_congr HEq, mul_comm, ← sub_add]
#align integral_log integral_log

@[simp]
theorem integral_log_of_pos (ha : 0 < a) (hb : 0 < b) :
    (∫ x in a..b, log x) = b * log b - a * log a - b + a :=
  integral_log <| not_mem_uIcc_of_lt ha hb
#align integral_log_of_pos integral_log_of_pos

@[simp]
theorem integral_log_of_neg (ha : a < 0) (hb : b < 0) :
    (∫ x in a..b, log x) = b * log b - a * log a - b + a :=
  integral_log <| not_mem_uIcc_of_gt ha hb
#align integral_log_of_neg integral_log_of_neg

@[simp]
theorem integral_sin : (∫ x in a..b, sin x) = cos a - cos b := by
  rw [integral_deriv_eq_sub' fun x => -cos x] <;> norm_num [continuous_on_sin]
#align integral_sin integral_sin

@[simp]
theorem integral_cos : (∫ x in a..b, cos x) = sin b - sin a := by
  rw [integral_deriv_eq_sub'] <;> norm_num [continuous_on_cos]
#align integral_cos integral_cos

theorem integral_cos_mul_complex {z : ℂ} (hz : z ≠ 0) (a b : ℝ) :
    (∫ x in a..b, Complex.cos (z * x)) = Complex.sin (z * b) / z - Complex.sin (z * a) / z :=
  by
  apply integral_eq_sub_of_has_deriv_at
  swap
  · apply Continuous.intervalIntegrable
    exact complex.continuous_cos.comp (continuous_const.mul Complex.continuous_of_real)
  intro x hx
  have a := Complex.hasDerivAt_sin (↑x * z)
  have b : HasDerivAt (fun y => y * z : ℂ → ℂ) z ↑x := hasDerivAt_mul_const _
  have c : HasDerivAt (fun y : ℂ => Complex.sin (y * z)) _ ↑x := HasDerivAt.comp x a b
  convert HasDerivAt.comp_of_real (c.div_const z)
  · simp_rw [mul_comm]
  · rw [mul_div_cancel _ hz, mul_comm]
#align integral_cos_mul_complex integral_cos_mul_complex

theorem integral_cos_sq_sub_sin_sq :
    (∫ x in a..b, cos x ^ 2 - sin x ^ 2) = sin b * cos b - sin a * cos a := by
  simpa only [sq, sub_eq_add_neg, neg_mul_eq_mul_neg] using
    integral_deriv_mul_eq_sub (fun x hx => has_deriv_at_sin x) (fun x hx => has_deriv_at_cos x)
      continuous_on_cos.interval_integrable continuous_on_sin.neg.interval_integrable
#align integral_cos_sq_sub_sin_sq integral_cos_sq_sub_sin_sq

@[simp]
theorem integral_inv_one_add_sq : (∫ x : ℝ in a..b, (1 + x ^ 2)⁻¹) = arctan b - arctan a :=
  by
  simp only [← one_div]
  refine' integral_deriv_eq_sub' _ _ _ (continuous_const.div _ fun x => _).ContinuousOn
  · norm_num
  · norm_num
  · continuity
  · nlinarith
#align integral_inv_one_add_sq integral_inv_one_add_sq

theorem integral_one_div_one_add_sq : (∫ x : ℝ in a..b, 1 / (1 + x ^ 2)) = arctan b - arctan a := by
  simp only [one_div, integral_inv_one_add_sq]
#align integral_one_div_one_add_sq integral_one_div_one_add_sq

section RpowCpow

open Complex

theorem integral_mul_cpow_one_add_sq {t : ℂ} (ht : t ≠ -1) :
    (∫ x : ℝ in a..b, (x : ℂ) * (1 + x ^ 2) ^ t) =
      (1 + b ^ 2) ^ (t + 1) / (2 * (t + 1)) - (1 + a ^ 2) ^ (t + 1) / (2 * (t + 1)) :=
  by
  have : t + 1 ≠ 0 := by
    contrapose! ht
    rwa [add_eq_zero_iff_eq_neg] at ht
  apply integral_eq_sub_of_has_deriv_at
  · intro x hx
    have f : HasDerivAt (fun y : ℂ => 1 + y ^ 2) (2 * x) x :=
      by
      convert(hasDerivAt_pow 2 (x : ℂ)).const_add 1
      · norm_cast
      · simp
    have g :
      ∀ {z : ℂ}, 0 < z.re → HasDerivAt (fun z => z ^ (t + 1) / (2 * (t + 1))) (z ^ t / 2) z :=
      by
      intro z hz
      have : z ≠ 0 := by
        contrapose! hz
        rw [hz, zero_re]
      convert(HasDerivAt.cpow_const (hasDerivAt_id _) (Or.inl hz)).div_const (2 * (t + 1)) using 1
      field_simp
      ring
    convert(HasDerivAt.comp (↑x) (g _) f).comp_of_real using 1
    · field_simp
      ring
    · rw [add_re, one_re, ← of_real_pow, of_real_re]
      exact add_pos_of_pos_of_nonneg zero_lt_one (sq_nonneg x)
  · apply Continuous.intervalIntegrable
    refine' continuous_of_real.mul _
    apply Continuous.cpow
    · exact continuous_const.add (continuous_of_real.pow 2)
    · exact continuous_const
    · intro a
      rw [add_re, one_re, ← of_real_pow, of_real_re]
      exact Or.inl (add_pos_of_pos_of_nonneg zero_lt_one (sq_nonneg a))
#align integral_mul_cpow_one_add_sq integral_mul_cpow_one_add_sq

theorem integral_mul_rpow_one_add_sq {t : ℝ} (ht : t ≠ -1) :
    (∫ x : ℝ in a..b, x * (1 + x ^ 2) ^ t) =
      (1 + b ^ 2) ^ (t + 1) / (2 * (t + 1)) - (1 + a ^ 2) ^ (t + 1) / (2 * (t + 1)) :=
  by
  have : ∀ x s : ℝ, (((1 + x ^ 2) ^ s : ℝ) : ℂ) = (1 + (x : ℂ) ^ 2) ^ ↑s :=
    by
    intro x s
    rw [of_real_cpow, of_real_add, of_real_pow, of_real_one]
    exact add_nonneg zero_le_one (sq_nonneg x)
  rw [← of_real_inj]
  convert integral_mul_cpow_one_add_sq (_ : (t : ℂ) ≠ -1)
  · rw [← intervalIntegral.integral_of_real]
    congr with x : 1
    rw [of_real_mul, this x t]
  · simp_rw [of_real_sub, of_real_div, this a (t + 1), this b (t + 1)]
    push_cast
  · rw [← of_real_one, ← of_real_neg, Ne.def, of_real_inj]
    exact ht
#align integral_mul_rpow_one_add_sq integral_mul_rpow_one_add_sq

end RpowCpow

/-! ### Integral of `sin x ^ n` -/

theorem integral_sin_pow_aux :
    (∫ x in a..b, sin x ^ (n + 2)) =
      (sin a ^ (n + 1) * cos a - sin b ^ (n + 1) * cos b + (n + 1) * ∫ x in a..b, sin x ^ n) -
        (n + 1) * ∫ x in a..b, sin x ^ (n + 2) :=
  by
  let C := sin a ^ (n + 1) * cos a - sin b ^ (n + 1) * cos b
  have h : ∀ α β γ : ℝ, β * α * γ * α = β * (α * α * γ) := fun α β γ => by ring
  have hu : ∀ x ∈ _, HasDerivAt (fun y => sin y ^ (n + 1)) ((n + 1 : ℕ) * cos x * sin x ^ n) x :=
    fun x hx => by simpa only [mul_right_comm] using (has_deriv_at_sin x).pow (n + 1)
  have hv : ∀ x ∈ [a, b], HasDerivAt (-cos) (sin x) x := fun x hx => by
    simpa only [neg_neg] using (has_deriv_at_cos x).neg
  have H := integral_mul_deriv_eq_deriv_mul hu hv _ _
  calc
    (∫ x in a..b, sin x ^ (n + 2)) = ∫ x in a..b, sin x ^ (n + 1) * sin x := by
      simp only [pow_succ']
    _ = C + (n + 1) * ∫ x in a..b, cos x ^ 2 * sin x ^ n := by simp [H, h, sq]
    _ = C + (n + 1) * ∫ x in a..b, sin x ^ n - sin x ^ (n + 2) := by
      simp [cos_sq', sub_mul, ← pow_add, add_comm]
    _ = (C + (n + 1) * ∫ x in a..b, sin x ^ n) - (n + 1) * ∫ x in a..b, sin x ^ (n + 2) := by
      rw [integral_sub, mul_sub, add_sub_assoc] <;> apply Continuous.intervalIntegrable <;>
        continuity

  all_goals apply Continuous.intervalIntegrable; continuity
#align integral_sin_pow_aux integral_sin_pow_aux

/-- The reduction formula for the integral of `sin x ^ n` for any natural `n ≥ 2`. -/
theorem integral_sin_pow :
    (∫ x in a..b, sin x ^ (n + 2)) =
      (sin a ^ (n + 1) * cos a - sin b ^ (n + 1) * cos b) / (n + 2) +
        (n + 1) / (n + 2) * ∫ x in a..b, sin x ^ n :=
  by
  have : (n : ℝ) + 2 ≠ 0 := by exact_mod_cast succ_ne_zero n.succ
  field_simp
  convert eq_sub_iff_add_eq.mp (integral_sin_pow_aux n)
  ring
#align integral_sin_pow integral_sin_pow

@[simp]
theorem integral_sin_sq : (∫ x in a..b, sin x ^ 2) = (sin a * cos a - sin b * cos b + b - a) / 2 :=
  by field_simp [integral_sin_pow, add_sub_assoc]
#align integral_sin_sq integral_sin_sq

theorem integral_sin_pow_odd :
    (∫ x in 0 ..π, sin x ^ (2 * n + 1)) = 2 * ∏ i in range n, (2 * i + 2) / (2 * i + 3) :=
  by
  induction' n with k ih; · norm_num
  rw [prod_range_succ_comm, mul_left_comm, ← ih, mul_succ, integral_sin_pow]
  norm_cast
  simp [-cast_add, field_simps]
#align integral_sin_pow_odd integral_sin_pow_odd

theorem integral_sin_pow_even :
    (∫ x in 0 ..π, sin x ^ (2 * n)) = π * ∏ i in range n, (2 * i + 1) / (2 * i + 2) :=
  by
  induction' n with k ih; · simp
  rw [prod_range_succ_comm, mul_left_comm, ← ih, mul_succ, integral_sin_pow]
  norm_cast
  simp [-cast_add, field_simps]
#align integral_sin_pow_even integral_sin_pow_even

theorem integral_sin_pow_pos : 0 < ∫ x in 0 ..π, sin x ^ n := by
  rcases even_or_odd' n with ⟨k, rfl | rfl⟩ <;>
          simp only [integral_sin_pow_even, integral_sin_pow_odd] <;>
        refine' mul_pos (by norm_num [pi_pos] ) (prod_pos fun n hn => div_pos _ _) <;>
      norm_cast <;>
    linarith
#align integral_sin_pow_pos integral_sin_pow_pos

theorem integral_sin_pow_succ_le : (∫ x in 0 ..π, sin x ^ (n + 1)) ≤ ∫ x in 0 ..π, sin x ^ n :=
  by
  let H x h := pow_le_pow_of_le_one (sin_nonneg_of_mem_Icc h) (sin_le_one x) (n.le_add_right 1)
  refine' integral_mono_on pi_pos.le _ _ H <;> exact (continuous_sin.pow _).IntervalIntegrable 0 π
#align integral_sin_pow_succ_le integral_sin_pow_succ_le

theorem integral_sin_pow_antitone : Antitone fun n : ℕ => ∫ x in 0 ..π, sin x ^ n :=
  antitone_nat_of_succ_le integral_sin_pow_succ_le
#align integral_sin_pow_antitone integral_sin_pow_antitone

/-! ### Integral of `cos x ^ n` -/

theorem integral_cos_pow_aux :
    (∫ x in a..b, cos x ^ (n + 2)) =
      (cos b ^ (n + 1) * sin b - cos a ^ (n + 1) * sin a + (n + 1) * ∫ x in a..b, cos x ^ n) -
        (n + 1) * ∫ x in a..b, cos x ^ (n + 2) :=
  by
  let C := cos b ^ (n + 1) * sin b - cos a ^ (n + 1) * sin a
  have h : ∀ α β γ : ℝ, β * α * γ * α = β * (α * α * γ) := fun α β γ => by ring
  have hu : ∀ x ∈ _, HasDerivAt (fun y => cos y ^ (n + 1)) (-(n + 1 : ℕ) * sin x * cos x ^ n) x :=
    fun x hx => by
    simpa only [mul_right_comm, neg_mul, mul_neg] using (has_deriv_at_cos x).pow (n + 1)
  have hv : ∀ x ∈ [a, b], HasDerivAt sin (cos x) x := fun x hx => has_deriv_at_sin x
  have H := integral_mul_deriv_eq_deriv_mul hu hv _ _
  calc
    (∫ x in a..b, cos x ^ (n + 2)) = ∫ x in a..b, cos x ^ (n + 1) * cos x := by
      simp only [pow_succ']
    _ = C + (n + 1) * ∫ x in a..b, sin x ^ 2 * cos x ^ n := by simp [H, h, sq, -neg_add_rev]
    _ = C + (n + 1) * ∫ x in a..b, cos x ^ n - cos x ^ (n + 2) := by
      simp [sin_sq, sub_mul, ← pow_add, add_comm]
    _ = (C + (n + 1) * ∫ x in a..b, cos x ^ n) - (n + 1) * ∫ x in a..b, cos x ^ (n + 2) := by
      rw [integral_sub, mul_sub, add_sub_assoc] <;> apply Continuous.intervalIntegrable <;>
        continuity

  all_goals apply Continuous.intervalIntegrable; continuity
#align integral_cos_pow_aux integral_cos_pow_aux

/-- The reduction formula for the integral of `cos x ^ n` for any natural `n ≥ 2`. -/
theorem integral_cos_pow :
    (∫ x in a..b, cos x ^ (n + 2)) =
      (cos b ^ (n + 1) * sin b - cos a ^ (n + 1) * sin a) / (n + 2) +
        (n + 1) / (n + 2) * ∫ x in a..b, cos x ^ n :=
  by
  have : (n : ℝ) + 2 ≠ 0 := by exact_mod_cast succ_ne_zero n.succ
  field_simp
  convert eq_sub_iff_add_eq.mp (integral_cos_pow_aux n)
  ring
#align integral_cos_pow integral_cos_pow

@[simp]
theorem integral_cos_sq : (∫ x in a..b, cos x ^ 2) = (cos b * sin b - cos a * sin a + b - a) / 2 :=
  by field_simp [integral_cos_pow, add_sub_assoc]
#align integral_cos_sq integral_cos_sq

/-! ### Integral of `sin x ^ m * cos x ^ n` -/

/-- Simplification of the integral of `sin x ^ m * cos x ^ n`, case `n` is odd. -/
theorem integral_sin_pow_mul_cos_pow_odd (m n : ℕ) :
    (∫ x in a..b, sin x ^ m * cos x ^ (2 * n + 1)) = ∫ u in sin a..sin b, u ^ m * (1 - u ^ 2) ^ n :=
  have hc : Continuous fun u : ℝ => u ^ m * (1 - u ^ 2) ^ n := by continuity
  calc
    (∫ x in a..b, sin x ^ m * cos x ^ (2 * n + 1)) =
        ∫ x in a..b, sin x ^ m * (1 - sin x ^ 2) ^ n * cos x :=
      by simp only [pow_succ', ← mul_assoc, pow_mul, cos_sq']
    _ = ∫ u in sin a..sin b, u ^ m * (1 - u ^ 2) ^ n :=
      integral_comp_mul_deriv (fun x hx => hasDerivAt_sin x) continuousOn_cos hc

#align integral_sin_pow_mul_cos_pow_odd integral_sin_pow_mul_cos_pow_odd

/-- The integral of `sin x * cos x`, given in terms of sin².
  See `integral_sin_mul_cos₂` below for the integral given in terms of cos². -/
@[simp]
theorem integral_sin_mul_cos₁ : (∫ x in a..b, sin x * cos x) = (sin b ^ 2 - sin a ^ 2) / 2 := by
  simpa using integral_sin_pow_mul_cos_pow_odd 1 0
#align integral_sin_mul_cos₁ integral_sin_mul_cos₁

@[simp]
theorem integral_sin_sq_mul_cos : (∫ x in a..b, sin x ^ 2 * cos x) = (sin b ^ 3 - sin a ^ 3) / 3 :=
  by simpa using integral_sin_pow_mul_cos_pow_odd 2 0
#align integral_sin_sq_mul_cos integral_sin_sq_mul_cos

@[simp]
theorem integral_cos_pow_three :
    (∫ x in a..b, cos x ^ 3) = sin b - sin a - (sin b ^ 3 - sin a ^ 3) / 3 := by
  simpa using integral_sin_pow_mul_cos_pow_odd 0 1
#align integral_cos_pow_three integral_cos_pow_three

/-- Simplification of the integral of `sin x ^ m * cos x ^ n`, case `m` is odd. -/
theorem integral_sin_pow_odd_mul_cos_pow (m n : ℕ) :
    (∫ x in a..b, sin x ^ (2 * m + 1) * cos x ^ n) = ∫ u in cos b..cos a, u ^ n * (1 - u ^ 2) ^ m :=
  have hc : Continuous fun u : ℝ => u ^ n * (1 - u ^ 2) ^ m := by continuity
  calc
    (∫ x in a..b, sin x ^ (2 * m + 1) * cos x ^ n) =
        -∫ x in b..a, sin x ^ (2 * m + 1) * cos x ^ n :=
      by rw [integral_symm]
    _ = ∫ x in b..a, (1 - cos x ^ 2) ^ m * -sin x * cos x ^ n := by
      simp [pow_succ', pow_mul, sin_sq]
    _ = ∫ x in b..a, cos x ^ n * (1 - cos x ^ 2) ^ m * -sin x :=
      by
      congr
      ext
      ring
    _ = ∫ u in cos b..cos a, u ^ n * (1 - u ^ 2) ^ m :=
      integral_comp_mul_deriv (fun x hx => hasDerivAt_cos x) continuousOn_sin.neg hc

#align integral_sin_pow_odd_mul_cos_pow integral_sin_pow_odd_mul_cos_pow

/-- The integral of `sin x * cos x`, given in terms of cos².
See `integral_sin_mul_cos₁` above for the integral given in terms of sin². -/
theorem integral_sin_mul_cos₂ : (∫ x in a..b, sin x * cos x) = (cos a ^ 2 - cos b ^ 2) / 2 := by
  simpa using integral_sin_pow_odd_mul_cos_pow 0 1
#align integral_sin_mul_cos₂ integral_sin_mul_cos₂

@[simp]
theorem integral_sin_mul_cos_sq : (∫ x in a..b, sin x * cos x ^ 2) = (cos a ^ 3 - cos b ^ 3) / 3 :=
  by simpa using integral_sin_pow_odd_mul_cos_pow 0 2
#align integral_sin_mul_cos_sq integral_sin_mul_cos_sq

@[simp]
theorem integral_sin_pow_three :
    (∫ x in a..b, sin x ^ 3) = cos a - cos b - (cos a ^ 3 - cos b ^ 3) / 3 := by
  simpa using integral_sin_pow_odd_mul_cos_pow 1 0
#align integral_sin_pow_three integral_sin_pow_three

/-- Simplification of the integral of `sin x ^ m * cos x ^ n`, case `m` and `n` are both even. -/
theorem integral_sin_pow_even_mul_cos_pow_even (m n : ℕ) :
    (∫ x in a..b, sin x ^ (2 * m) * cos x ^ (2 * n)) =
      ∫ x in a..b, ((1 - cos (2 * x)) / 2) ^ m * ((1 + cos (2 * x)) / 2) ^ n :=
  by field_simp [pow_mul, sin_sq, cos_sq, ← sub_sub, (by ring : (2 : ℝ) - 1 = 1)]
#align integral_sin_pow_even_mul_cos_pow_even integral_sin_pow_even_mul_cos_pow_even

@[simp]
theorem integral_sin_sq_mul_cos_sq :
    (∫ x in a..b, sin x ^ 2 * cos x ^ 2) = (b - a) / 8 - (sin (4 * b) - sin (4 * a)) / 32 :=
  by
  convert integral_sin_pow_even_mul_cos_pow_even 1 1 using 1
  have h1 : ∀ c : ℝ, (1 - c) / 2 * ((1 + c) / 2) = (1 - c ^ 2) / 4 := fun c => by ring
  have h2 : Continuous fun x => cos (2 * x) ^ 2 := by continuity
  have h3 : ∀ x, cos x * sin x = sin (2 * x) / 2 :=
    by
    intro
    rw [sin_two_mul]
    ring
  have h4 : ∀ d : ℝ, 2 * (2 * d) = 4 * d := fun d => by ring
  simp [h1, h2.interval_integrable, integral_comp_mul_left fun x => cos x ^ 2, h3, h4]
  ring
#align integral_sin_sq_mul_cos_sq integral_sin_sq_mul_cos_sq


/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Jeremy Avigad, Haitao Zhang

Finite unions and intersections on finsets.

Note: for the moment we only do unions. We need to generalize bigops for intersections.
-/
import data.finset.comb algebra.group_bigops
open list

namespace finset

variables {A B : Type} [deceqA : decidable_eq A] [deceqB : decidable_eq B]

/- Unionl and Union -/

section union

definition to_comm_monoid_Union (B : Type) [decidable_eq B] :
  comm_monoid (finset B) :=
⦃ comm_monoid,
  mul         := union,
  mul_assoc   := union_assoc,
  one         := empty,
  mul_one     := union_empty,
  one_mul     := empty_union,
  mul_comm    := union_comm
⦄

local attribute finset.to_comm_monoid_Union [instance]

include deceqB

definition Unionl (l : list A) (f : A → finset B) : finset B := Prodl l f
notation `⋃` binders `←` l, r:(scoped f, Unionl l f) := r
definition Union (s : finset A) (f : A → finset B) : finset B := finset.Prod s f
notation `⋃` binders `∈` s, r:(scoped f, finset.Union s f) := r

theorem Unionl_nil (f : A → finset B) : Unionl [] f = ∅ := Prodl_nil f
theorem Unionl_cons (f : A → finset B) (a : A) (l : list A) :
  Unionl (a::l) f = f a ∪ Unionl l f := Prodl_cons f a l
theorem Unionl_append (l₁ l₂ : list A) (f : A → finset B) :
  Unionl (l₁++l₂) f = Unionl l₁ f ∪ Unionl l₂ f := Prodl_append l₁ l₂ f
theorem Unionl_mul (l : list A) (f g : A → finset B) :
  Unionl l (λx, f x ∪ g x) = Unionl l f ∪ Unionl l g := Prodl_mul l f g
section deceqA
  include deceqA
  theorem Unionl_insert_of_mem (f : A → finset B) {a : A} {l : list A} (H : a ∈ l) :
    Unionl (list.insert a l) f = Unionl l f := Prodl_insert_of_mem f H
  theorem Unionl_insert_of_not_mem (f : A → finset B) {a : A} {l : list A} (H : a ∉ l) :
    Unionl (list.insert a l) f = f a ∪ Unionl l f := Prodl_insert_of_not_mem f H
  theorem Unionl_union {l₁ l₂ : list A} (f : A → finset B) (d : list.disjoint l₁ l₂) :
    Unionl (list.union l₁ l₂) f = Unionl l₁ f ∪ Unionl l₂ f := Prodl_union f d
  theorem Unionl_empty (l : list A) : Unionl l (λ x, ∅) = (∅ : finset B) := Prodl_one l
end deceqA

theorem Union_empty (f : A → finset B) : Union ∅ f = ∅ := finset.Prod_empty f
theorem Union_mul (s : finset A) (f g : A → finset B) :
  Union s (λx, f x ∪ g x) = Union s f ∪ Union s g := finset.Prod_mul s f g
section deceqA
  include deceqA
  theorem Union_insert_of_mem (f : A → finset B) {a : A} {s : finset A} (H : a ∈ s) :
    Union (insert a s) f = Union s f := finset.Prod_insert_of_mem f H
  private theorem Union_insert_of_not_mem (f : A → finset B) {a : A} {s : finset A} (H : a ∉ s) :
    Union (insert a s) f = f a ∪ Union s f := finset.Prod_insert_of_not_mem f H
  theorem Union_union (f : A → finset B) {s₁ s₂ : finset A} (disj : s₁ ∩ s₂ = ∅) :
    Union (s₁ ∪ s₂) f = Union s₁ f ∪ Union s₂ f := finset.Prod_union f disj
  theorem Union_ext {s : finset A} {f g : A → finset B} (H : ∀x, x ∈ s → f x = g x) :
    Union s f = Union s g := finset.Prod_ext H
  theorem Union_empty' (s : finset A) : Union s (λ x, ∅) = (∅ : finset B) := finset.Prod_one s

  -- this will eventually be an instance of something more general
  theorem inter_Union (s : finset B) (t : finset A) (f : A → finset B) :
      s ∩ (⋃ x ∈ t, f x) = (⋃ x ∈ t, s ∩ f x) :=
  begin
    induction t with s' x H IH,
      rewrite [*Union_empty, inter_empty],
      rewrite [*Union_insert_of_not_mem _ H, inter_distrib_left, IH],
  end

  theorem mem_Union_iff (s : finset A) (f : A → finset B) (b : B) :
    b ∈ (⋃ x ∈ s, f x) ↔ (∃ x, x ∈ s ∧ b ∈ f x ) :=
  begin
    induction s with s' a H IH,
      rewrite [exists_mem_empty_eq],
      rewrite [Union_insert_of_not_mem _ H, mem_union_eq, IH, exists_mem_insert_eq]
  end

  theorem mem_Union_eq (s : finset A) (f : A → finset B) (b : B) :
    b ∈ (⋃ x ∈ s, f x) = (∃ x, x ∈ s ∧ b ∈ f x ) :=
  propext !mem_Union_iff

  theorem Union_insert (f : A → finset B) {a : A} {s : finset A} :
    Union (insert a s) f = f a ∪ Union s f :=
  decidable.by_cases
    (assume Pin : a ∈ s,
      begin
        rewrite [Union_insert_of_mem f Pin],
        apply ext,
        intro x,
        apply iff.intro,
          exact mem_union_r,
          rewrite [mem_union_eq],
          intro Por,
          exact or.elim Por
            (assume Pl, begin
              rewrite mem_Union_eq, exact (exists.intro a (and.intro Pin Pl)) end)
            (assume Pr, Pr)
      end)
    (assume H : a ∉ s, !Union_insert_of_not_mem H)

  lemma image_eq_Union_index_image (s : finset A) (f : A → finset B) :
    Union s f = Union (image f s) id :=
      finset.induction_on s
        (by rewrite Union_empty)
        (take s1 a Pa IH, by rewrite [image_insert, *Union_insert, IH])

  lemma Union_const [decidable_eq B] {f : A → finset B} {s : finset A} {t : finset B} :
    s ≠ ∅ → (∀ x, x ∈ s → f x = t) → Union s f = t :=
  begin
    induction s with a' s' H IH,
      {intros [H1, H2], exfalso, apply H1 !rfl},
    intros [H1, H2],
    rewrite [Union_insert, H2 _ !mem_insert],
    cases (decidable.em (s' = ∅)) with [seq, sne],
      {rewrite [seq, Union_empty, union_empty]},
    have H3 : ∀ x, x ∈ s' → f x = t, from (λ x H', H2 x (mem_insert_of_mem _ H')),
    rewrite [IH sne H3, union_self]
  end
end deceqA

end union

end finset