/-
Copyright (c) 2022 Jujian Zhang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jujian Zhang, Eric Wieser
-/

import ring_theory.graded_algebra.homogeneous_ideal

/-!

This file contains a proof that the radical of any homogeneous ideal is a homogeneous ideal

## Main statements

* `ideal.is_homogeneous.is_prime_iff`: for any `I : ideal A`, if `I` is homogeneous, then
  `I` is prime if and only if `I` is homogeneously prime, i.e. `I ≠ ⊤` and if `x, y` are
  homogeneous elements such that `x * y ∈ I`, then at least one of `x,y` is in `I`.
* `ideal.is_prime.homogeneous_core`: for any `I : ideal A`, if `I` is prime, then
  `I.homogeneous_core 𝒜` (i.e. the largest homogeneous ideal contained in `I`) is also prime.
* `ideal.is_homogeneous.radical`: for any `I : ideal A`, if `I` is homogeneous, then the
  radical of `I` is homogeneous as well.
* `homogeneous_ideal.radical`: for any `I : homogeneous_ideal 𝒜`, `I.radical` is the the
  radical of `I` as a `homogeneous_ideal 𝒜`

## Implementation details

Throughout this file, the indexing type `ι` of grading is assumed to be a
`linear_ordered_cancel_add_comm_monoid`. This might be stronger than necessary but cancelling
property is strictly necessary; for a counterexample of how `ideal.is_homogeneous.is_prime_iff`
fails for a non-cancellative set see `counterexample/homogeneous_prime_not_prime.lean`.

## Tags

homogeneous, radical
-/

open graded_algebra direct_sum set_like finset
open_locale big_operators

variables {ι R A : Type*}
variables [comm_semiring R] [comm_ring A] [algebra R A]
variables [linear_ordered_cancel_add_comm_monoid ι]
variables {𝒜 : ι → submodule R A} [graded_algebra 𝒜]

lemma ideal.is_homogeneous.is_prime_of_homogeneous_mem_or_mem
  {I : ideal A} (hI : I.is_homogeneous 𝒜) (I_ne_top : I ≠ ⊤)
  (homogeneous_mem_or_mem : ∀ {x y : A},
    is_homogeneous 𝒜 x → is_homogeneous 𝒜 y → (x * y ∈ I → x ∈ I ∨ y ∈ I)) :
  ideal.is_prime I :=
⟨I_ne_top, begin
  intros x y hxy,
  by_contradiction rid,
  obtain ⟨rid₁, rid₂⟩ := not_or_distrib.mp rid,
  /-
  The idea of the proof is the following :
  since `x * y ∈ I` and `I` homogeneous, then `proj i (x * y) ∈ I` for any `i : ι`.
  Then consider two sets `{i ∈ x.support | xᵢ ∉ I}` and `{j ∈ y.support | yⱼ ∉ J}`;
  let `max₁, max₂` be the maximum of the two sets, then `proj (max₁ + max₂) (x * y) ∈ I`.
  Then, `proj max₁ x ∉ I` and `proj max₂ j ∉ I`
  but `proj i x ∈ I` for all `max₁ < i` and `proj j y ∈ I` for all `max₂ < j`.
  `  proj (max₁ + max₂) (x * y)`
  `= ∑ {(i, j) ∈ supports | i + j = max₁ + max₂}, xᵢ * yⱼ`
  `= proj max₁ x * proj max₂ y`
  `  + ∑ {(i, j) ∈ supports \ {(max₁, max₂)} | i + j = max₁ + max₂}, xᵢ * yⱼ`.
  This is a contradiction, because both `proj (max₁ + max₂) (x * y) ∈ I` and the sum on the
  right hand side is in `I` however `proj max₁ x * proj max₂ y` is not in `I`.
  -/
  classical,
  set set₁ := (decompose 𝒜 x).support.filter (λ i, proj 𝒜 i x ∉ I) with set₁_eq,
  set set₂ := (decompose 𝒜 y).support.filter (λ i, proj 𝒜 i y ∉ I) with set₂_eq,
  have nonempty :
    ∀ (x : A), (x ∉ I) → ((decompose 𝒜 x).support.filter (λ i, proj 𝒜 i x ∉ I)).nonempty,
  { intros x hx,
    rw filter_nonempty_iff,
    contrapose! hx,
    simp_rw proj_apply at hx,
    rw ← sum_support_decompose 𝒜 x,
    exact ideal.sum_mem _ hx, },
  set max₁ := set₁.max' (nonempty x rid₁) with max₁_eq,
  set max₂ := set₂.max' (nonempty y rid₂) with max₂_eq,
  have mem_max₁ : max₁ ∈ set₁ := max'_mem set₁ (nonempty x rid₁),
  have mem_max₂ : max₂ ∈ set₂ := max'_mem set₂ (nonempty y rid₂),
  replace hxy : proj 𝒜 (max₁ + max₂) (x * y) ∈ I := hI _ hxy,

  have mem_I : proj 𝒜 max₁ x * proj 𝒜 max₂ y ∈ I,
  { set antidiag :=
      ((decompose 𝒜 x).support.product (decompose 𝒜 y).support)
        .filter (λ z : ι × ι, z.1 + z.2 = max₁ + max₂) with ha,
    have mem_antidiag : (max₁, max₂) ∈ antidiag,
    { simp only [add_sum_erase, mem_filter, mem_product],
      exact ⟨⟨mem_of_mem_filter _ mem_max₁, mem_of_mem_filter _ mem_max₂⟩, rfl⟩ },
    have eq_add_sum :=
      calc  proj 𝒜 (max₁ + max₂) (x * y)
          = ∑ ij in antidiag, proj 𝒜 ij.1 x * proj 𝒜 ij.2 y
          : by simp_rw [ha, proj_apply, direct_sum.decompose_mul,
                        direct_sum.coe_mul_apply 𝒜]
      ... = proj 𝒜 max₁ x * proj 𝒜 max₂ y + ∑ ij in antidiag.erase (max₁, max₂),
                                              proj 𝒜 ij.1 x * proj 𝒜 ij.2 y
          : (add_sum_erase _ _ mem_antidiag).symm,
    rw eq_sub_of_add_eq eq_add_sum.symm,
    refine ideal.sub_mem _ hxy (ideal.sum_mem _ (λ z H, _)),
    rcases z with ⟨i, j⟩,
    simp only [mem_erase, prod.mk.inj_iff, ne.def, mem_filter, mem_product] at H,
    rcases H with ⟨H₁, ⟨H₂, H₃⟩, H₄⟩,
    have max_lt : max₁ < i ∨ max₂ < j,
    { rcases lt_trichotomy max₁ i with h | rfl | h,
      { exact or.inl h },
      { refine false.elim (H₁ ⟨rfl, add_left_cancel H₄⟩), },
      { apply or.inr,
        have := add_lt_add_right h j,
        rw H₄ at this,
        exact lt_of_add_lt_add_left this, }, },
    cases max_lt,
    { -- in this case `max₁ < i`, then `xᵢ ∈ I`; for otherwise `i ∈ set₁` then `i ≤ max₁`.
      have not_mem : i ∉ set₁ := λ h, lt_irrefl _
        ((max'_lt_iff set₁ (nonempty x rid₁)).mp max_lt i h),
      rw set₁_eq at not_mem,
      simp only [not_and, not_not, ne.def, mem_filter] at not_mem,
      exact ideal.mul_mem_right _ I (not_mem H₂), },
    { -- in this case  `max₂ < j`, then `yⱼ ∈ I`; for otherwise `j ∈ set₂`, then `j ≤ max₂`.
      have not_mem : j ∉ set₂ := λ h, lt_irrefl _
        ((max'_lt_iff set₂ (nonempty y rid₂)).mp max_lt j h),
      rw set₂_eq at not_mem,
      simp only [not_and, not_not, ne.def, mem_filter] at not_mem,
      exact ideal.mul_mem_left I _ (not_mem H₃), }, },

  have not_mem_I : proj 𝒜 max₁ x * proj 𝒜 max₂ y ∉ I,
  { have neither_mem : proj 𝒜 max₁ x ∉ I ∧ proj 𝒜 max₂ y ∉ I,
    { rw mem_filter at mem_max₁ mem_max₂,
      exact ⟨mem_max₁.2, mem_max₂.2⟩, },
    intro rid,
    cases homogeneous_mem_or_mem ⟨max₁, submodule.coe_mem _⟩ ⟨max₂, submodule.coe_mem _⟩ mem_I,
    { apply neither_mem.1 h },
    { apply neither_mem.2 h }, },

  exact not_mem_I mem_I,
end⟩

lemma ideal.is_homogeneous.is_prime_iff {I : ideal A} (h : I.is_homogeneous 𝒜) :
  I.is_prime ↔
  (I ≠ ⊤) ∧
    ∀ {x y : A}, set_like.is_homogeneous 𝒜 x → set_like.is_homogeneous 𝒜 y
      → (x * y ∈ I → x ∈ I ∨ y ∈ I) :=
⟨λ HI,
  ⟨ne_of_apply_ne _ HI.ne_top, λ x y hx hy hxy, ideal.is_prime.mem_or_mem HI hxy⟩,
  λ ⟨I_ne_top, homogeneous_mem_or_mem⟩,
    h.is_prime_of_homogeneous_mem_or_mem I_ne_top @homogeneous_mem_or_mem⟩

lemma ideal.is_prime.homogeneous_core {I : ideal A} (h : I.is_prime) :
  (I.homogeneous_core 𝒜).to_ideal.is_prime :=
begin
  apply (ideal.homogeneous_core 𝒜 I).is_homogeneous.is_prime_of_homogeneous_mem_or_mem,
  { exact ne_top_of_le_ne_top h.ne_top (ideal.to_ideal_homogeneous_core_le 𝒜 I) },
  rintros x y hx hy hxy,
  have H := h.mem_or_mem (ideal.to_ideal_homogeneous_core_le 𝒜 I hxy),
  refine H.imp _ _,
  { exact ideal.mem_homogeneous_core_of_is_homogeneous_of_mem hx, },
  { exact ideal.mem_homogeneous_core_of_is_homogeneous_of_mem hy, },
end

lemma ideal.is_homogeneous.radical_eq {I : ideal A} (hI : I.is_homogeneous 𝒜) :
  I.radical = Inf { J | J.is_homogeneous 𝒜 ∧ I ≤ J ∧ J.is_prime } :=
begin
  rw ideal.radical_eq_Inf,
  apply le_antisymm,
  { exact Inf_le_Inf (λ J, and.right), },
  { refine Inf_le_Inf_of_forall_exists_le _,
    rintros J ⟨HJ₁, HJ₂⟩,
    refine ⟨(J.homogeneous_core 𝒜).to_ideal, _, J.to_ideal_homogeneous_core_le _⟩,
    refine ⟨homogeneous_ideal.is_homogeneous _, _, HJ₂.homogeneous_core⟩,
    refine hI.to_ideal_homogeneous_core_eq_self.symm.trans_le (ideal.homogeneous_core_mono _ HJ₁), }
end

lemma ideal.is_homogeneous.radical {I : ideal A} (h : I.is_homogeneous 𝒜)  :
  I.radical.is_homogeneous 𝒜 :=
by { rw h.radical_eq, exact ideal.is_homogeneous.Inf (λ _, and.left) }

/-- The radical of a homogenous ideal, as another homogenous ideal. -/
def homogeneous_ideal.radical (I : homogeneous_ideal 𝒜) : homogeneous_ideal 𝒜 :=
⟨I.to_ideal.radical, I.is_homogeneous.radical⟩

@[simp]
lemma homogeneous_ideal.coe_radical (I : homogeneous_ideal 𝒜) :
  I.radical.to_ideal = I.to_ideal.radical := rfl


/-

Idea:
A rank function is just a submodular function which is weakly increasing,
and sandwiched between 0 and size.

-/

import boolalg

structure submod (b : boolalg.boolalg) :=
  (f : b → ℤ)
  (s : forall (X Y : b), f (X ∪ Y) + f (X ∩ Y) ≤ f X + f Y)
instance submod.coe_to_fun {b : boolalg.boolalg} : has_coe_to_fun (submod b) := ⟨_, submod.f⟩

structure rankfun (b : boolalg.boolalg) extends submod b :=
  (pos : forall (X : b), 0 ≤ f X)
  (monotone : forall (X Y : b), X ⊆ Y → f(X) ≤ f(Y))
instance rankfun.coe_to_fun {b : boolalg.boolalg} : has_coe_to_fun (rankfun b) := ⟨_, fun (r : rankfun b), r.f⟩

structure boolalg.embed_ (b b' : boolalg.boolalg) :=
  (f : b → b')
  (on_bot : f ⊥ = ⊥)
  -- note : top is not respected by embedding
  (on_inter (X Y) : f (X ∩ Y) = (f X) ∩ (f Y))
  (on_union (X Y) : f (X ∪ Y) = (f X) ∪ (f Y))
  -- note : compl is not respected by embedding
  (on_size (X : b) : boolalg.size X = boolalg.size (f X))

lemma boolalg.embed.on_subset_ {b b' : boolalg.boolalg} (emb : boolalg.embed b b') {X Y : b} :
  (X ⊆ Y) → (emb.f X) ⊆ (emb.f Y) := sorry

def powersetalg_ : Type → boolalg.boolalg := sorry
def subalg_ {b : boolalg.boolalg} (E : b) : boolalg.boolalg := sorry
instance boolalg.embed.coe_to_fun_ {b b' : boolalg.boolalg} : has_coe_to_fun (boolalg.embed b b') := {
  F := (fun _, b → b'),
  coe := sorry,
}
def subalg_.embed_ {b : boolalg.boolalg} {E : b} : boolalg.embed (subalg_ E) b := sorry

structure matroid :=
  (E : Type)
  (r : rankfun (powersetalg_ E))
def matroid.alg_ (M : matroid) := powersetalg_ M.E

structure preminor (M : matroid) :=
  (E : M.alg_)
  (C : M.alg_)
  (disjoint : C ∩ {e} = ⊥)

def preminor.r {M : matroid} (N : preminor M) : rankfun (subalg_ N.E) :=
let emb := @subalg_.embed_ M.alg_ N.E, ef := emb.f, C := N.C in
{
  f := (fun (X : subalg_ N.E), M.r (ef X ∪ N.C) - M.r N.C),
  s :=
  begin
    intros X Y,
    have hu : (ef X ∪ C) ∪ (ef Y ∪ C) = ef (X ∪ Y) ∪ C := by rw ←boolalg.union_distrib_union_left; rw ←emb.on_union,
    have hi : (ef X ∪ C) ∩ (ef Y ∪ C) = ef (X ∩ Y) ∪ C := by sorry, --rw ←boolalg.union_distrib_left; rw ←emb.on_inter,
    have hs := M.r.s (ef X ∪ C) (ef Y ∪ C),
    rw [hu, hi] at hs, unfold_coes,
    linarith [hs],
  end,
  pos := sorry,
  monotone := begin
    intros X Y hXY,
    have := M.r.monotone (ef X ∪ C) (ef Y ∪ C) (boolalg.subset_union_subset_left (ef X) (ef Y) C (emb.on_subset hXY )),
    show M.r (ef X ∪ N.C) - M.r N.C ≤ M.r (ef Y ∪ N.C) - M.r N.C,
    unfold_coes,
    linarith,
  end
}

/-
begin
    intros X Y, rcases m.kernel with ⟨C,emb,⟨h0,hC,hCr⟩⟩,
    let f := emb.func,
    have hu : (f X ∪ C) ∪ (f Y ∪ C) = f (X ∪ Y) ∪ C := by rw ←union_distrib_union_left; rw ←emb.on_union,
    have hi : (f X ∪ C) ∩ (f Y ∪ C) = f (X ∩ Y) ∪ C := by rw ←union_distrib_left; rw ←emb.on_inter,
    have hs := M.s (f X ∪ C) (f Y ∪ C),
    rw [hu, hi] at hs,
    linarith [hCr X, hCr Y, hCr (X ∪ Y), hCr (X ∩ Y), hR3],
  end,
-/

def minor (M : matroid) : Type* :=
  quot (fun (N₁ N₂ : preminor M), N₁.r = N₂.r)

--def minor.alg {M: matroid} (N : minor M) :

structure protominor (M : matroid) :=
  (E : M.alg_)
  (r : rankfun (subalg_ E))

def preminor_to_protominor {M : matroid} (N : preminor M) : protominor M := {
  E := N.E,
  r := N.r,
}

def preminor_to_protominor_respects_support {M : matroid} (N₁ N₂ : preminor M) (h : N₁.r = N₂.r) : preminor_to_protominor N₁ = preminor_to_protominor N₂
    := sorry
#check quot.lift
-- power_set_alg {γ : Type} [decidable_eq γ] (S : finset γ) : boolalg


/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kunhao Zheng, Stanislas Polu, David Renshaw, OpenAI GPT-f
-/
import mathzoo.imports.miniF2F

open_locale nat rat real big_operators topological_space

theorem mathd_numbertheory_335
  (n : ℕ)
  (h₀ : n % 7 = 5) :
  (5 * n) % 7 = 4 :=
begin
  norm_num [nat.mul_mod, h₀],
end


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.complex.exponential
import Mathlib.analysis.calculus.mean_value
import Mathlib.measure_theory.borel_space
import Mathlib.analysis.complex.real_deriv
import Mathlib.PostPort

universes u_1

namespace Mathlib

/-!
# Complex and real exponential, real logarithm

## Main statements

This file establishes the basic analytical properties of the complex and real exponential functions
(continuity, differentiability, computation of the derivative).

It also contains the definition of the real logarithm function (as the inverse of the
exponential on `(0, +∞)`, extended to `ℝ` by setting `log (-x) = log x`) and its basic
properties (continuity, differentiability, formula for the derivative).

The complex logarithm is *not* defined in this file as it relies on trigonometric functions. See
instead `trigonometric.lean`.

## Tags

exp, log
-/

namespace complex

/-- The complex exponential is everywhere differentiable, with the derivative `exp x`. -/
theorem has_deriv_at_exp (x : ℂ) : has_deriv_at exp (exp x) x := sorry

theorem differentiable_exp : differentiable ℂ exp :=
  fun (x : ℂ) => has_deriv_at.differentiable_at (has_deriv_at_exp x)

theorem differentiable_at_exp {x : ℂ} : differentiable_at ℂ exp x := differentiable_exp x

@[simp] theorem deriv_exp : deriv exp = exp :=
  funext fun (x : ℂ) => has_deriv_at.deriv (has_deriv_at_exp x)

@[simp] theorem iter_deriv_exp (n : ℕ) : nat.iterate deriv n exp = exp := sorry

theorem continuous_exp : continuous exp := differentiable.continuous differentiable_exp

theorem times_cont_diff_exp {n : with_top ℕ} : times_cont_diff ℂ n exp := sorry

theorem measurable_exp : measurable exp := continuous.measurable continuous_exp

end complex

theorem has_deriv_at.cexp {f : ℂ → ℂ} {f' : ℂ} {x : ℂ} (hf : has_deriv_at f f' x) :
    has_deriv_at (fun (x : ℂ) => complex.exp (f x)) (complex.exp (f x) * f') x :=
  has_deriv_at.comp x (complex.has_deriv_at_exp (f x)) hf

theorem has_deriv_within_at.cexp {f : ℂ → ℂ} {f' : ℂ} {x : ℂ} {s : set ℂ}
    (hf : has_deriv_within_at f f' s x) :
    has_deriv_within_at (fun (x : ℂ) => complex.exp (f x)) (complex.exp (f x) * f') s x :=
  has_deriv_at.comp_has_deriv_within_at x (complex.has_deriv_at_exp (f x)) hf

theorem deriv_within_cexp {f : ℂ → ℂ} {x : ℂ} {s : set ℂ} (hf : differentiable_within_at ℂ f s x)
    (hxs : unique_diff_within_at ℂ s x) :
    deriv_within (fun (x : ℂ) => complex.exp (f x)) s x = complex.exp (f x) * deriv_within f s x :=
  has_deriv_within_at.deriv_within
    (has_deriv_within_at.cexp (differentiable_within_at.has_deriv_within_at hf)) hxs

@[simp] theorem deriv_cexp {f : ℂ → ℂ} {x : ℂ} (hc : differentiable_at ℂ f x) :
    deriv (fun (x : ℂ) => complex.exp (f x)) x = complex.exp (f x) * deriv f x :=
  has_deriv_at.deriv (has_deriv_at.cexp (differentiable_at.has_deriv_at hc))

theorem measurable.cexp {α : Type u_1} [measurable_space α] {f : α → ℂ} (hf : measurable f) :
    measurable fun (x : α) => complex.exp (f x) :=
  measurable.comp complex.measurable_exp hf

theorem has_fderiv_within_at.cexp {E : Type u_1} [normed_group E] [normed_space ℂ E] {f : E → ℂ}
    {f' : continuous_linear_map ℂ E ℂ} {x : E} {s : set E} (hf : has_fderiv_within_at f f' s x) :
    has_fderiv_within_at (fun (x : E) => complex.exp (f x)) (complex.exp (f x) • f') s x :=
  has_deriv_at.comp_has_fderiv_within_at x (complex.has_deriv_at_exp (f x)) hf

theorem has_fderiv_at.cexp {E : Type u_1} [normed_group E] [normed_space ℂ E] {f : E → ℂ}
    {f' : continuous_linear_map ℂ E ℂ} {x : E} (hf : has_fderiv_at f f' x) :
    has_fderiv_at (fun (x : E) => complex.exp (f x)) (complex.exp (f x) • f') x :=
  iff.mp has_fderiv_within_at_univ
    (has_fderiv_within_at.cexp (has_fderiv_at.has_fderiv_within_at hf))

theorem differentiable_within_at.cexp {E : Type u_1} [normed_group E] [normed_space ℂ E] {f : E → ℂ}
    {x : E} {s : set E} (hf : differentiable_within_at ℂ f s x) :
    differentiable_within_at ℂ (fun (x : E) => complex.exp (f x)) s x :=
  has_fderiv_within_at.differentiable_within_at
    (has_fderiv_within_at.cexp (differentiable_within_at.has_fderiv_within_at hf))

@[simp] theorem differentiable_at.cexp {E : Type u_1} [normed_group E] [normed_space ℂ E]
    {f : E → ℂ} {x : E} (hc : differentiable_at ℂ f x) :
    differentiable_at ℂ (fun (x : E) => complex.exp (f x)) x :=
  has_fderiv_at.differentiable_at (has_fderiv_at.cexp (differentiable_at.has_fderiv_at hc))

theorem differentiable_on.cexp {E : Type u_1} [normed_group E] [normed_space ℂ E] {f : E → ℂ}
    {s : set E} (hc : differentiable_on ℂ f s) :
    differentiable_on ℂ (fun (x : E) => complex.exp (f x)) s :=
  fun (x : E) (h : x ∈ s) => differentiable_within_at.cexp (hc x h)

@[simp] theorem differentiable.cexp {E : Type u_1} [normed_group E] [normed_space ℂ E] {f : E → ℂ}
    (hc : differentiable ℂ f) : differentiable ℂ fun (x : E) => complex.exp (f x) :=
  fun (x : E) => differentiable_at.cexp (hc x)

theorem times_cont_diff.cexp {E : Type u_1} [normed_group E] [normed_space ℂ E] {f : E → ℂ}
    {n : with_top ℕ} (h : times_cont_diff ℂ n f) :
    times_cont_diff ℂ n fun (x : E) => complex.exp (f x) :=
  times_cont_diff.comp complex.times_cont_diff_exp h

theorem times_cont_diff_at.cexp {E : Type u_1} [normed_group E] [normed_space ℂ E] {f : E → ℂ}
    {x : E} {n : with_top ℕ} (hf : times_cont_diff_at ℂ n f x) :
    times_cont_diff_at ℂ n (fun (x : E) => complex.exp (f x)) x :=
  times_cont_diff_at.comp x (times_cont_diff.times_cont_diff_at complex.times_cont_diff_exp) hf

theorem times_cont_diff_on.cexp {E : Type u_1} [normed_group E] [normed_space ℂ E] {f : E → ℂ}
    {s : set E} {n : with_top ℕ} (hf : times_cont_diff_on ℂ n f s) :
    times_cont_diff_on ℂ n (fun (x : E) => complex.exp (f x)) s :=
  times_cont_diff.comp_times_cont_diff_on complex.times_cont_diff_exp hf

theorem times_cont_diff_within_at.cexp {E : Type u_1} [normed_group E] [normed_space ℂ E]
    {f : E → ℂ} {x : E} {s : set E} {n : with_top ℕ} (hf : times_cont_diff_within_at ℂ n f s x) :
    times_cont_diff_within_at ℂ n (fun (x : E) => complex.exp (f x)) s x :=
  times_cont_diff_at.comp_times_cont_diff_within_at x
    (times_cont_diff.times_cont_diff_at complex.times_cont_diff_exp) hf

namespace real

theorem has_deriv_at_exp (x : ℝ) : has_deriv_at exp (exp x) x :=
  has_deriv_at.real_of_complex (complex.has_deriv_at_exp ↑x)

theorem times_cont_diff_exp {n : with_top ℕ} : times_cont_diff ℝ n exp :=
  times_cont_diff.real_of_complex complex.times_cont_diff_exp

theorem differentiable_exp : differentiable ℝ exp :=
  fun (x : ℝ) => has_deriv_at.differentiable_at (has_deriv_at_exp x)

theorem differentiable_at_exp {x : ℝ} : differentiable_at ℝ exp x := differentiable_exp x

@[simp] theorem deriv_exp : deriv exp = exp :=
  funext fun (x : ℝ) => has_deriv_at.deriv (has_deriv_at_exp x)

@[simp] theorem iter_deriv_exp (n : ℕ) : nat.iterate deriv n exp = exp := sorry

theorem continuous_exp : continuous exp := differentiable.continuous differentiable_exp

theorem measurable_exp : measurable exp := continuous.measurable continuous_exp

end real

/-! Register lemmas for the derivatives of the composition of `real.exp` with a differentiable
function, for standalone use and use with `simp`. -/

theorem has_deriv_at.exp {f : ℝ → ℝ} {f' : ℝ} {x : ℝ} (hf : has_deriv_at f f' x) :
    has_deriv_at (fun (x : ℝ) => real.exp (f x)) (real.exp (f x) * f') x :=
  has_deriv_at.comp x (real.has_deriv_at_exp (f x)) hf

theorem has_deriv_within_at.exp {f : ℝ → ℝ} {f' : ℝ} {x : ℝ} {s : set ℝ}
    (hf : has_deriv_within_at f f' s x) :
    has_deriv_within_at (fun (x : ℝ) => real.exp (f x)) (real.exp (f x) * f') s x :=
  has_deriv_at.comp_has_deriv_within_at x (real.has_deriv_at_exp (f x)) hf

theorem deriv_within_exp {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} (hf : differentiable_within_at ℝ f s x)
    (hxs : unique_diff_within_at ℝ s x) :
    deriv_within (fun (x : ℝ) => real.exp (f x)) s x = real.exp (f x) * deriv_within f s x :=
  has_deriv_within_at.deriv_within
    (has_deriv_within_at.exp (differentiable_within_at.has_deriv_within_at hf)) hxs

@[simp] theorem deriv_exp {f : ℝ → ℝ} {x : ℝ} (hc : differentiable_at ℝ f x) :
    deriv (fun (x : ℝ) => real.exp (f x)) x = real.exp (f x) * deriv f x :=
  has_deriv_at.deriv (has_deriv_at.exp (differentiable_at.has_deriv_at hc))

/-! Register lemmas for the derivatives of the composition of `real.exp` with a differentiable
function, for standalone use and use with `simp`. -/

theorem measurable.exp {α : Type u_1} [measurable_space α] {f : α → ℝ} (hf : measurable f) :
    measurable fun (x : α) => real.exp (f x) :=
  measurable.comp real.measurable_exp hf

theorem times_cont_diff.exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {n : with_top ℕ} (hf : times_cont_diff ℝ n f) :
    times_cont_diff ℝ n fun (x : E) => real.exp (f x) :=
  times_cont_diff.comp real.times_cont_diff_exp hf

theorem times_cont_diff_at.exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {x : E} {n : with_top ℕ} (hf : times_cont_diff_at ℝ n f x) :
    times_cont_diff_at ℝ n (fun (x : E) => real.exp (f x)) x :=
  times_cont_diff_at.comp x (times_cont_diff.times_cont_diff_at real.times_cont_diff_exp) hf

theorem times_cont_diff_on.exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {s : set E} {n : with_top ℕ} (hf : times_cont_diff_on ℝ n f s) :
    times_cont_diff_on ℝ n (fun (x : E) => real.exp (f x)) s :=
  times_cont_diff.comp_times_cont_diff_on real.times_cont_diff_exp hf

theorem times_cont_diff_within_at.exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {x : E} {s : set E} {n : with_top ℕ} (hf : times_cont_diff_within_at ℝ n f s x) :
    times_cont_diff_within_at ℝ n (fun (x : E) => real.exp (f x)) s x :=
  times_cont_diff_at.comp_times_cont_diff_within_at x
    (times_cont_diff.times_cont_diff_at real.times_cont_diff_exp) hf

theorem has_fderiv_within_at.exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {f' : continuous_linear_map ℝ E ℝ} {x : E} {s : set E} (hf : has_fderiv_within_at f f' s x) :
    has_fderiv_within_at (fun (x : E) => real.exp (f x)) (real.exp (f x) • f') s x :=
  sorry

theorem has_fderiv_at.exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {f' : continuous_linear_map ℝ E ℝ} {x : E} (hf : has_fderiv_at f f' x) :
    has_fderiv_at (fun (x : E) => real.exp (f x)) (real.exp (f x) • f') x :=
  iff.mp has_fderiv_within_at_univ
    (has_fderiv_within_at.exp (has_fderiv_at.has_fderiv_within_at hf))

theorem differentiable_within_at.exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {x : E} {s : set E} (hf : differentiable_within_at ℝ f s x) :
    differentiable_within_at ℝ (fun (x : E) => real.exp (f x)) s x :=
  has_fderiv_within_at.differentiable_within_at
    (has_fderiv_within_at.exp (differentiable_within_at.has_fderiv_within_at hf))

@[simp] theorem differentiable_at.exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {x : E} (hc : differentiable_at ℝ f x) :
    differentiable_at ℝ (fun (x : E) => real.exp (f x)) x :=
  has_fderiv_at.differentiable_at (has_fderiv_at.exp (differentiable_at.has_fderiv_at hc))

theorem differentiable_on.exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {s : set E} (hc : differentiable_on ℝ f s) :
    differentiable_on ℝ (fun (x : E) => real.exp (f x)) s :=
  fun (x : E) (h : x ∈ s) => differentiable_within_at.exp (hc x h)

@[simp] theorem differentiable.exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    (hc : differentiable ℝ f) : differentiable ℝ fun (x : E) => real.exp (f x) :=
  fun (x : E) => differentiable_at.exp (hc x)

theorem fderiv_within_exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ} {x : E}
    {s : set E} (hf : differentiable_within_at ℝ f s x) (hxs : unique_diff_within_at ℝ s x) :
    fderiv_within ℝ (fun (x : E) => real.exp (f x)) s x = real.exp (f x) • fderiv_within ℝ f s x :=
  has_fderiv_within_at.fderiv_within
    (has_fderiv_within_at.exp (differentiable_within_at.has_fderiv_within_at hf)) hxs

@[simp] theorem fderiv_exp {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ} {x : E}
    (hc : differentiable_at ℝ f x) :
    fderiv ℝ (fun (x : E) => real.exp (f x)) x = real.exp (f x) • fderiv ℝ f x :=
  has_fderiv_at.fderiv (has_fderiv_at.exp (differentiable_at.has_fderiv_at hc))

namespace real

/-- The real exponential function tends to `+∞` at `+∞`. -/
theorem tendsto_exp_at_top : filter.tendsto exp filter.at_top filter.at_top :=
  filter.tendsto_at_top_mono' filter.at_top
    (iff.mpr filter.eventually_at_top
      (Exists.intro 0 fun (x : ℝ) (hx : x ≥ 0) => add_one_le_exp_of_nonneg hx))
    (filter.tendsto_at_top_add_const_right filter.at_top 1 filter.tendsto_id)

/-- The real exponential function tends to `0` at `-∞` or, equivalently, `exp(-x)` tends to `0`
at `+∞` -/
theorem tendsto_exp_neg_at_top_nhds_0 :
    filter.tendsto (fun (x : ℝ) => exp (-x)) filter.at_top (nhds 0) :=
  filter.tendsto.congr (fun (x : ℝ) => Eq.symm (exp_neg x))
    (filter.tendsto.comp tendsto_inv_at_top_zero tendsto_exp_at_top)

/-- The real exponential function tends to `1` at `0`. -/
theorem tendsto_exp_nhds_0_nhds_1 : filter.tendsto exp (nhds 0) (nhds 1) := sorry

theorem tendsto_exp_at_bot : filter.tendsto exp filter.at_bot (nhds 0) :=
  filter.tendsto.congr (fun (x : ℝ) => congr_arg exp (neg_neg x))
    (filter.tendsto.comp tendsto_exp_neg_at_top_nhds_0 filter.tendsto_neg_at_bot_at_top)

theorem tendsto_exp_at_bot_nhds_within :
    filter.tendsto exp filter.at_bot (nhds_within 0 (set.Ioi 0)) :=
  iff.mpr filter.tendsto_inf
    { left := tendsto_exp_at_bot,
      right := iff.mpr filter.tendsto_principal (filter.eventually_of_forall exp_pos) }

/-- `real.exp` as an order isomorphism between `ℝ` and `(0, +∞)`. -/
def exp_order_iso : ℝ ≃o ↥(set.Ioi 0) :=
  strict_mono.order_iso_of_surjective (set.cod_restrict exp (set.Ioi 0) exp_pos) sorry sorry

@[simp] theorem coe_exp_order_iso_apply (x : ℝ) : ↑(coe_fn exp_order_iso x) = exp x := rfl

@[simp] theorem coe_comp_exp_order_iso : coe ∘ ⇑exp_order_iso = exp := rfl

@[simp] theorem range_exp : set.range exp = set.Ioi 0 := sorry

@[simp] theorem map_exp_at_top : filter.map exp filter.at_top = filter.at_top := sorry

@[simp] theorem comap_exp_at_top : filter.comap exp filter.at_top = filter.at_top := sorry

@[simp] theorem tendsto_exp_comp_at_top {α : Type u_1} {l : filter α} {f : α → ℝ} :
    filter.tendsto (fun (x : α) => exp (f x)) l filter.at_top ↔ filter.tendsto f l filter.at_top :=
  sorry

theorem tendsto_comp_exp_at_top {α : Type u_1} {l : filter α} {f : ℝ → α} :
    filter.tendsto (fun (x : ℝ) => f (exp x)) filter.at_top l ↔ filter.tendsto f filter.at_top l :=
  sorry

@[simp] theorem map_exp_at_bot : filter.map exp filter.at_bot = nhds_within 0 (set.Ioi 0) := sorry

theorem comap_exp_nhds_within_Ioi_zero :
    filter.comap exp (nhds_within 0 (set.Ioi 0)) = filter.at_bot :=
  sorry

theorem tendsto_comp_exp_at_bot {α : Type u_1} {l : filter α} {f : ℝ → α} :
    filter.tendsto (fun (x : ℝ) => f (exp x)) filter.at_bot l ↔
        filter.tendsto f (nhds_within 0 (set.Ioi 0)) l :=
  sorry

/-- The real logarithm function, equal to the inverse of the exponential for `x > 0`,
to `log |x|` for `x < 0`, and to `0` for `0`. We use this unconventional extension to
`(-∞, 0]` as it gives the formula `log (x * y) = log x + log y` for all nonzero `x` and `y`, and
the derivative of `log` is `1/x` away from `0`. -/
def log (x : ℝ) : ℝ :=
  dite (x = 0) (fun (hx : x = 0) => 0)
    fun (hx : ¬x = 0) => coe_fn (order_iso.symm exp_order_iso) { val := abs x, property := sorry }

theorem log_of_ne_zero {x : ℝ} (hx : x ≠ 0) :
    log x =
        coe_fn (order_iso.symm exp_order_iso) { val := abs x, property := iff.mpr abs_pos hx } :=
  dif_neg hx

theorem log_of_pos {x : ℝ} (hx : 0 < x) :
    log x = coe_fn (order_iso.symm exp_order_iso) { val := x, property := hx } :=
  sorry

theorem exp_log_eq_abs {x : ℝ} (hx : x ≠ 0) : exp (log x) = abs x := sorry

theorem exp_log {x : ℝ} (hx : 0 < x) : exp (log x) = x :=
  eq.mpr (id (Eq._oldrec (Eq.refl (exp (log x) = x)) (exp_log_eq_abs (has_lt.lt.ne' hx))))
    (abs_of_pos hx)

theorem exp_log_of_neg {x : ℝ} (hx : x < 0) : exp (log x) = -x :=
  eq.mpr (id (Eq._oldrec (Eq.refl (exp (log x) = -x)) (exp_log_eq_abs (ne_of_lt hx))))
    (abs_of_neg hx)

@[simp] theorem log_exp (x : ℝ) : log (exp x) = x := exp_injective (exp_log (exp_pos x))

theorem surj_on_log : set.surj_on log (set.Ioi 0) set.univ :=
  fun (x : ℝ) (_x : x ∈ set.univ) => Exists.intro (exp x) { left := exp_pos x, right := log_exp x }

theorem log_surjective : function.surjective log := fun (x : ℝ) => Exists.intro (exp x) (log_exp x)

@[simp] theorem range_log : set.range log = set.univ := function.surjective.range_eq log_surjective

@[simp] theorem log_zero : log 0 = 0 := dif_pos rfl

@[simp] theorem log_one : log 1 = 0 :=
  exp_injective
    (eq.mpr (id (Eq._oldrec (Eq.refl (exp (log 1) = exp 0)) (exp_log zero_lt_one)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (1 = exp 0)) exp_zero)) (Eq.refl 1)))

@[simp] theorem log_abs (x : ℝ) : log (abs x) = log x := sorry

@[simp] theorem log_neg_eq_log (x : ℝ) : log (-x) = log x :=
  eq.mpr (id (Eq._oldrec (Eq.refl (log (-x) = log x)) (Eq.symm (log_abs x))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (log (-x) = log (abs x))) (Eq.symm (log_abs (-x)))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (log (abs (-x)) = log (abs x))) (abs_neg x)))
        (Eq.refl (log (abs x)))))

theorem surj_on_log' : set.surj_on log (set.Iio 0) set.univ := sorry

theorem log_mul {x : ℝ} {y : ℝ} (hx : x ≠ 0) (hy : y ≠ 0) : log (x * y) = log x + log y := sorry

@[simp] theorem log_inv (x : ℝ) : log (x⁻¹) = -log x := sorry

theorem log_le_log {x : ℝ} {y : ℝ} (h : 0 < x) (h₁ : 0 < y) : log x ≤ log y ↔ x ≤ y :=
  eq.mpr (id (Eq._oldrec (Eq.refl (log x ≤ log y ↔ x ≤ y)) (Eq.symm (propext exp_le_exp))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (exp (log x) ≤ exp (log y) ↔ x ≤ y)) (exp_log h)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (x ≤ exp (log y) ↔ x ≤ y)) (exp_log h₁)))
        (iff.refl (x ≤ y))))

theorem log_lt_log {x : ℝ} {y : ℝ} (hx : 0 < x) : x < y → log x < log y := sorry

theorem log_lt_log_iff {x : ℝ} {y : ℝ} (hx : 0 < x) (hy : 0 < y) : log x < log y ↔ x < y :=
  eq.mpr (id (Eq._oldrec (Eq.refl (log x < log y ↔ x < y)) (Eq.symm (propext exp_lt_exp))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (exp (log x) < exp (log y) ↔ x < y)) (exp_log hx)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (x < exp (log y) ↔ x < y)) (exp_log hy)))
        (iff.refl (x < y))))

theorem log_pos_iff {x : ℝ} (hx : 0 < x) : 0 < log x ↔ 1 < x :=
  eq.mpr (id (Eq._oldrec (Eq.refl (0 < log x ↔ 1 < x)) (Eq.symm log_one)))
    (log_lt_log_iff zero_lt_one hx)

theorem log_pos {x : ℝ} (hx : 1 < x) : 0 < log x :=
  iff.mpr (log_pos_iff (lt_trans zero_lt_one hx)) hx

theorem log_neg_iff {x : ℝ} (h : 0 < x) : log x < 0 ↔ x < 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (log x < 0 ↔ x < 1)) (Eq.symm log_one)))
    (log_lt_log_iff h zero_lt_one)

theorem log_neg {x : ℝ} (h0 : 0 < x) (h1 : x < 1) : log x < 0 := iff.mpr (log_neg_iff h0) h1

theorem log_nonneg_iff {x : ℝ} (hx : 0 < x) : 0 ≤ log x ↔ 1 ≤ x :=
  eq.mpr (id (Eq._oldrec (Eq.refl (0 ≤ log x ↔ 1 ≤ x)) (Eq.symm (propext not_lt))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (¬log x < 0 ↔ 1 ≤ x)) (propext (log_neg_iff hx))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (¬x < 1 ↔ 1 ≤ x)) (propext not_lt))) (iff.refl (1 ≤ x))))

theorem log_nonneg {x : ℝ} (hx : 1 ≤ x) : 0 ≤ log x :=
  iff.mpr (log_nonneg_iff (has_lt.lt.trans_le zero_lt_one hx)) hx

theorem log_nonpos_iff {x : ℝ} (hx : 0 < x) : log x ≤ 0 ↔ x ≤ 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (log x ≤ 0 ↔ x ≤ 1)) (Eq.symm (propext not_lt))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (¬0 < log x ↔ x ≤ 1)) (propext (log_pos_iff hx))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (¬1 < x ↔ x ≤ 1)) (propext not_lt))) (iff.refl (x ≤ 1))))

theorem log_nonpos_iff' {x : ℝ} (hx : 0 ≤ x) : log x ≤ 0 ↔ x ≤ 1 := sorry

theorem log_nonpos {x : ℝ} (hx : 0 ≤ x) (h'x : x ≤ 1) : log x ≤ 0 :=
  iff.mpr (log_nonpos_iff' hx) h'x

theorem strict_mono_incr_on_log : strict_mono_incr_on log (set.Ioi 0) :=
  fun (x : ℝ) (hx : x ∈ set.Ioi 0) (y : ℝ) (hy : y ∈ set.Ioi 0) (hxy : x < y) => log_lt_log hx hxy

theorem strict_mono_decr_on_log : strict_mono_decr_on log (set.Iio 0) := sorry

/-- The real logarithm function tends to `+∞` at `+∞`. -/
theorem tendsto_log_at_top : filter.tendsto log filter.at_top filter.at_top := sorry

theorem tendsto_log_nhds_within_zero :
    filter.tendsto log (nhds_within 0 (singleton 0ᶜ)) filter.at_bot :=
  sorry

theorem continuous_on_log : continuous_on log (singleton 0ᶜ) := sorry

theorem continuous_log' : continuous fun (x : Subtype fun (x : ℝ) => 0 < x) => log ↑x := sorry

theorem continuous_at_log {x : ℝ} (hx : x ≠ 0) : continuous_at log x :=
  continuous_within_at.continuous_at (continuous_on_log x hx)
    (mem_nhds_sets is_open_compl_singleton hx)

@[simp] theorem continuous_at_log_iff {x : ℝ} : continuous_at log x ↔ x ≠ 0 := sorry

theorem has_deriv_at_log_of_pos {x : ℝ} (hx : 0 < x) : has_deriv_at log (x⁻¹) x := sorry

theorem has_deriv_at_log {x : ℝ} (hx : x ≠ 0) : has_deriv_at log (x⁻¹) x := sorry

theorem differentiable_at_log {x : ℝ} (hx : x ≠ 0) : differentiable_at ℝ log x :=
  has_deriv_at.differentiable_at (has_deriv_at_log hx)

theorem differentiable_on_log : differentiable_on ℝ log (singleton 0ᶜ) :=
  fun (x : ℝ) (hx : x ∈ (singleton 0ᶜ)) =>
    differentiable_at.differentiable_within_at (differentiable_at_log hx)

@[simp] theorem differentiable_at_log_iff {x : ℝ} : differentiable_at ℝ log x ↔ x ≠ 0 :=
  { mp :=
      fun (h : differentiable_at ℝ log x) =>
        iff.mp continuous_at_log_iff (differentiable_at.continuous_at h),
    mpr := differentiable_at_log }

theorem deriv_log (x : ℝ) : deriv log x = (x⁻¹) := sorry

@[simp] theorem deriv_log' : deriv log = has_inv.inv := funext deriv_log

theorem measurable_log : measurable log :=
  measurable_of_measurable_on_compl_singleton 0
    (continuous.measurable (iff.mp continuous_on_iff_continuous_restrict continuous_on_log))

theorem times_cont_diff_on_log {n : with_top ℕ} : times_cont_diff_on ℝ n log (singleton 0ᶜ) := sorry

theorem times_cont_diff_at_log {x : ℝ} (hx : x ≠ 0) {n : with_top ℕ} :
    times_cont_diff_at ℝ n log x :=
  times_cont_diff_within_at.times_cont_diff_at (times_cont_diff_on_log x hx)
    (mem_nhds_sets is_open_compl_singleton hx)

end real

theorem filter.tendsto.log {α : Type u_1} {f : α → ℝ} {l : filter α} {x : ℝ}
    (h : filter.tendsto f l (nhds x)) (hx : x ≠ 0) :
    filter.tendsto (fun (x : α) => real.log (f x)) l (nhds (real.log x)) :=
  filter.tendsto.comp (continuous_at.tendsto (real.continuous_at_log hx)) h

theorem continuous.log {α : Type u_1} [topological_space α] {f : α → ℝ} (hf : continuous f)
    (h₀ : ∀ (x : α), f x ≠ 0) : continuous fun (x : α) => real.log (f x) :=
  continuous_on.comp_continuous real.continuous_on_log hf h₀

theorem continuous_at.log {α : Type u_1} [topological_space α] {f : α → ℝ} {a : α}
    (hf : continuous_at f a) (h₀ : f a ≠ 0) : continuous_at (fun (x : α) => real.log (f x)) a :=
  filter.tendsto.log hf h₀

theorem continuous_within_at.log {α : Type u_1} [topological_space α] {f : α → ℝ} {s : set α}
    {a : α} (hf : continuous_within_at f s a) (h₀ : f a ≠ 0) :
    continuous_within_at (fun (x : α) => real.log (f x)) s a :=
  filter.tendsto.log hf h₀

theorem continuous_on.log {α : Type u_1} [topological_space α] {f : α → ℝ} {s : set α}
    (hf : continuous_on f s) (h₀ : ∀ (x : α), x ∈ s → f x ≠ 0) :
    continuous_on (fun (x : α) => real.log (f x)) s :=
  fun (x : α) (hx : x ∈ s) => continuous_within_at.log (hf x hx) (h₀ x hx)

theorem measurable.log {α : Type u_1} [measurable_space α] {f : α → ℝ} (hf : measurable f) :
    measurable fun (x : α) => real.log (f x) :=
  measurable.comp real.measurable_log hf

theorem has_deriv_within_at.log {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} {s : set ℝ}
    (hf : has_deriv_within_at f f' s x) (hx : f x ≠ 0) :
    has_deriv_within_at (fun (y : ℝ) => real.log (f y)) (f' / f x) s x :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (has_deriv_within_at (fun (y : ℝ) => real.log (f y)) (f' / f x) s x))
        div_eq_inv_mul))
    (has_deriv_at.comp_has_deriv_within_at x (real.has_deriv_at_log hx) hf)

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

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

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

theorem has_fderiv_within_at.log {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {x : E} {f' : continuous_linear_map ℝ E ℝ} {s : set E} (hf : has_fderiv_within_at f f' s x)
    (hx : f x ≠ 0) : has_fderiv_within_at (fun (x : E) => real.log (f x)) (f x⁻¹ • f') s x :=
  has_deriv_at.comp_has_fderiv_within_at x (real.has_deriv_at_log hx) hf

theorem has_fderiv_at.log {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ} {x : E}
    {f' : continuous_linear_map ℝ E ℝ} (hf : has_fderiv_at f f' x) (hx : f x ≠ 0) :
    has_fderiv_at (fun (x : E) => real.log (f x)) (f x⁻¹ • f') x :=
  has_deriv_at.comp_has_fderiv_at x (real.has_deriv_at_log hx) hf

theorem differentiable_within_at.log {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {x : E} {s : set E} (hf : differentiable_within_at ℝ f s x) (hx : f x ≠ 0) :
    differentiable_within_at ℝ (fun (x : E) => real.log (f x)) s x :=
  has_fderiv_within_at.differentiable_within_at
    (has_fderiv_within_at.log (differentiable_within_at.has_fderiv_within_at hf) hx)

@[simp] theorem differentiable_at.log {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {x : E} (hf : differentiable_at ℝ f x) (hx : f x ≠ 0) :
    differentiable_at ℝ (fun (x : E) => real.log (f x)) x :=
  has_fderiv_at.differentiable_at (has_fderiv_at.log (differentiable_at.has_fderiv_at hf) hx)

theorem differentiable_on.log {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    {s : set E} (hf : differentiable_on ℝ f s) (hx : ∀ (x : E), x ∈ s → f x ≠ 0) :
    differentiable_on ℝ (fun (x : E) => real.log (f x)) s :=
  fun (x : E) (h : x ∈ s) => differentiable_within_at.log (hf x h) (hx x h)

@[simp] theorem differentiable.log {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ}
    (hf : differentiable ℝ f) (hx : ∀ (x : E), f x ≠ 0) :
    differentiable ℝ fun (x : E) => real.log (f x) :=
  fun (x : E) => differentiable_at.log (hf x) (hx x)

theorem fderiv_within.log {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ} {x : E}
    {s : set E} (hf : differentiable_within_at ℝ f s x) (hx : f x ≠ 0)
    (hxs : unique_diff_within_at ℝ s x) :
    fderiv_within ℝ (fun (x : E) => real.log (f x)) s x = f x⁻¹ • fderiv_within ℝ f s x :=
  has_fderiv_within_at.fderiv_within
    (has_fderiv_within_at.log (differentiable_within_at.has_fderiv_within_at hf) hx) hxs

@[simp] theorem fderiv.log {E : Type u_1} [normed_group E] [normed_space ℝ E] {f : E → ℝ} {x : E}
    (hf : differentiable_at ℝ f x) (hx : f x ≠ 0) :
    fderiv ℝ (fun (x : E) => real.log (f x)) x = f x⁻¹ • fderiv ℝ f x :=
  has_fderiv_at.fderiv (has_fderiv_at.log (differentiable_at.has_fderiv_at hf) hx)

namespace real

/-- The function `exp(x)/x^n` tends to `+∞` at `+∞`, for any natural number `n` -/
theorem tendsto_exp_div_pow_at_top (n : ℕ) :
    filter.tendsto (fun (x : ℝ) => exp x / x ^ n) filter.at_top filter.at_top :=
  sorry

/-- The function `x^n * exp(-x)` tends to `0` at `+∞`, for any natural number `n`. -/
theorem tendsto_pow_mul_exp_neg_at_top_nhds_0 (n : ℕ) :
    filter.tendsto (fun (x : ℝ) => x ^ n * exp (-x)) filter.at_top (nhds 0) :=
  sorry

/-- The function `(b * exp x + c) / (x ^ n)` tends to `+∞` at `+∞`, for any positive natural number
`n` and any real numbers `b` and `c` such that `b` is positive. -/
theorem tendsto_mul_exp_add_div_pow_at_top (b : ℝ) (c : ℝ) (n : ℕ) (hb : 0 < b) (hn : 1 ≤ n) :
    filter.tendsto (fun (x : ℝ) => (b * exp x + c) / x ^ n) filter.at_top filter.at_top :=
  sorry

/-- The function `(x ^ n) / (b * exp x + c)` tends to `0` at `+∞`, for any positive natural number
`n` and any real numbers `b` and `c` such that `b` is nonzero. -/
theorem tendsto_div_pow_mul_exp_add_at_top (b : ℝ) (c : ℝ) (n : ℕ) (hb : 0 ≠ b) (hn : 1 ≤ n) :
    filter.tendsto (fun (x : ℝ) => x ^ n / (b * exp x + c)) filter.at_top (nhds 0) :=
  sorry

/-- A crude lemma estimating the difference between `log (1-x)` and its Taylor series at `0`,
where the main point of the bound is that it tends to `0`. The goal is to deduce the series
expansion of the logarithm, in `has_sum_pow_div_log_of_abs_lt_1`.
-/
theorem abs_log_sub_add_sum_range_le {x : ℝ} (h : abs x < 1) (n : ℕ) :
    abs ((finset.sum (finset.range n) fun (i : ℕ) => x ^ (i + 1) / (↑i + 1)) + log (1 - x)) ≤
        abs x ^ (n + 1) / (1 - abs x) :=
  sorry

/-- Power series expansion of the logarithm around `1`. -/
theorem has_sum_pow_div_log_of_abs_lt_1 {x : ℝ} (h : abs x < 1) :
    has_sum (fun (n : ℕ) => x ^ (n + 1) / (↑n + 1)) (-log (1 - x)) :=
  sorry

end Mathlib


/-
Copyright (c) 2021 Oliver Nash. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Oliver Nash, Eric Wieser
-/
import linear_algebra.determinant
import topology.algebra.infinite_sum
import topology.algebra.ring
import topology.algebra.star

/-!
# Topological properties of matrices

This file is a place to collect topological results about matrices.

## Main definitions:

* `matrix.topological_ring`: square matrices form a topological ring

## Main results

* Continuity:
  * `continuous.matrix_det`: the determinant is continuous over a topological ring.
  * `continuous.matrix_adjugate`: the adjugate is continuous over a topological ring.
* Infinite sums
  * `matrix.transpose_tsum`: transpose commutes with infinite sums
  * `matrix.diagonal_tsum`: diagonal commutes with infinite sums
  * `matrix.block_diagonal_tsum`: block diagonal commutes with infinite sums
  * `matrix.block_diagonal'_tsum`: non-uniform block diagonal commutes with infinite sums
-/

open matrix
open_locale matrix

variables {X α l m n p S R : Type*} {m' n' : l → Type*}

instance [topological_space R] : topological_space (matrix m n R) := Pi.topological_space

instance [topological_space R] [t2_space R] : t2_space (matrix m n R) := Pi.t2_space

/-! ### Lemmas about continuity of operations -/
section continuity
variables [topological_space X] [topological_space R]

instance [has_smul α R] [has_continuous_const_smul α R] :
  has_continuous_const_smul α (matrix m n R) :=
pi.has_continuous_const_smul

instance [topological_space α] [has_smul α R] [has_continuous_smul α R] :
  has_continuous_smul α (matrix m n R) :=
pi.has_continuous_smul

instance [has_add R] [has_continuous_add R] : has_continuous_add (matrix m n R) :=
pi.has_continuous_add

instance [has_neg R] [has_continuous_neg R] : has_continuous_neg (matrix m n R) :=
pi.has_continuous_neg

instance [add_group R] [topological_add_group R] : topological_add_group (matrix m n R) :=
pi.topological_add_group

/-- To show a function into matrices is continuous it suffices to show the coefficients of the
resulting matrix are continuous -/
@[continuity]
lemma continuous_matrix [topological_space α] {f : α → matrix m n R}
  (h : ∀ i j, continuous (λ a, f a i j)) : continuous f :=
continuous_pi $ λ _, continuous_pi $ λ j, h _ _

lemma continuous.matrix_elem {A : X → matrix m n R} (hA : continuous A) (i : m) (j : n) :
  continuous (λ x, A x i j) :=
(continuous_apply_apply i j).comp hA

@[continuity]
lemma continuous.matrix_map [topological_space S] {A : X → matrix m n S} {f : S → R}
   (hA : continuous A) (hf : continuous f) :
  continuous (λ x, (A x).map f) :=
continuous_matrix $ λ i j, hf.comp $ hA.matrix_elem _ _

@[continuity]
lemma continuous.matrix_transpose {A : X → matrix m n R} (hA : continuous A) :
  continuous (λ x, (A x)ᵀ) :=
continuous_matrix $ λ i j, hA.matrix_elem j i

lemma continuous.matrix_conj_transpose [has_star R] [has_continuous_star R] {A : X → matrix m n R}
  (hA : continuous A) : continuous (λ x, (A x)ᴴ) :=
hA.matrix_transpose.matrix_map continuous_star

instance [has_star R] [has_continuous_star R] : has_continuous_star (matrix m m R) :=
⟨continuous_id.matrix_conj_transpose⟩

@[continuity]
lemma continuous.matrix_col {A : X → n → R} (hA : continuous A) : continuous (λ x, col (A x)) :=
continuous_matrix $ λ i j, (continuous_apply _).comp hA

@[continuity]
lemma continuous.matrix_row {A : X → n → R} (hA : continuous A) : continuous (λ x, row (A x)) :=
continuous_matrix $ λ i j, (continuous_apply _).comp hA

@[continuity]
lemma continuous.matrix_diagonal [has_zero R] [decidable_eq n] {A : X → n → R} (hA : continuous A) :
  continuous (λ x, diagonal (A x)) :=
continuous_matrix $ λ i j, ((continuous_apply i).comp hA).if_const _ continuous_zero

@[continuity]
lemma continuous.matrix_dot_product [fintype n] [has_mul R] [add_comm_monoid R]
  [has_continuous_add R] [has_continuous_mul R]
  {A : X → n → R} {B : X → n → R} (hA : continuous A) (hB : continuous B) :
  continuous (λ x, dot_product (A x) (B x)) :=
continuous_finset_sum _ $ λ i _, ((continuous_apply i).comp hA).mul ((continuous_apply i).comp hB)

/-- For square matrices the usual `continuous_mul` can be used. -/
@[continuity]
lemma continuous.matrix_mul [fintype n] [has_mul R] [add_comm_monoid R] [has_continuous_add R]
  [has_continuous_mul R]
  {A : X → matrix m n R} {B : X → matrix n p R} (hA : continuous A) (hB : continuous B) :
  continuous (λ x, (A x).mul (B x)) :=
continuous_matrix $ λ i j, continuous_finset_sum _ $ λ k _,
  (hA.matrix_elem _ _).mul (hB.matrix_elem _ _)

instance [fintype n] [has_mul R] [add_comm_monoid R] [has_continuous_add R]
  [has_continuous_mul R] : has_continuous_mul (matrix n n R) :=
⟨continuous_fst.matrix_mul continuous_snd⟩

instance [fintype n] [non_unital_non_assoc_semiring R] [topological_semiring R] :
  topological_semiring (matrix n n R) :=
{}

instance [fintype n] [non_unital_non_assoc_ring R] [topological_ring R] :
  topological_ring (matrix n n R) :=
{}

@[continuity]
lemma continuous.matrix_vec_mul_vec [has_mul R] [has_continuous_mul R]
  {A : X → m → R} {B : X → n → R} (hA : continuous A) (hB : continuous B) :
  continuous (λ x, vec_mul_vec (A x) (B x)) :=
continuous_matrix $ λ i j, ((continuous_apply _).comp hA).mul ((continuous_apply _).comp hB)

@[continuity]
lemma continuous.matrix_mul_vec [non_unital_non_assoc_semiring R] [has_continuous_add R]
  [has_continuous_mul R] [fintype n]
  {A : X → matrix m n R} {B : X → n → R} (hA : continuous A) (hB : continuous B) :
  continuous (λ x, (A x).mul_vec (B x)) :=
continuous_pi $ λ i, ((continuous_apply i).comp hA).matrix_dot_product hB

@[continuity]
lemma continuous.matrix_vec_mul [non_unital_non_assoc_semiring R] [has_continuous_add R]
  [has_continuous_mul R] [fintype m]
  {A : X → m → R} {B : X → matrix m n R} (hA : continuous A) (hB : continuous B) :
  continuous (λ x, vec_mul (A x) (B x)) :=
continuous_pi $ λ i, hA.matrix_dot_product $ continuous_pi $ λ j, hB.matrix_elem _ _

@[continuity]
lemma continuous.matrix_minor {A : X → matrix l n R} (hA : continuous A) (e₁ : m → l) (e₂ : p → n) :
  continuous (λ x, (A x).minor e₁ e₂) :=
continuous_matrix $ λ i j, hA.matrix_elem _ _

@[continuity]
lemma continuous.matrix_reindex {A : X → matrix l n R}
  (hA : continuous A) (e₁ : l ≃ m) (e₂ : n ≃ p) :
  continuous (λ x, reindex e₁ e₂ (A x)) :=
hA.matrix_minor _ _

@[continuity]
lemma continuous.matrix_diag {A : X → matrix n n R} (hA : continuous A) :
  continuous (λ x, matrix.diag (A x)) :=
continuous_pi $ λ _, hA.matrix_elem _ _

-- note this doesn't elaborate well from the above
lemma continuous_matrix_diag : continuous (matrix.diag : matrix n n R → n → R) :=
show continuous (λ x : matrix n n R, matrix.diag x), from continuous_id.matrix_diag

@[continuity]
lemma continuous.matrix_trace [fintype n] [add_comm_monoid R] [has_continuous_add R]
  {A : X → matrix n n R} (hA : continuous A) :
  continuous (λ x, trace (A x)) :=
continuous_finset_sum _ $ λ i hi, hA.matrix_elem _ _

@[continuity]
lemma continuous.matrix_det [fintype n] [decidable_eq n] [comm_ring R] [topological_ring R]
  {A : X → matrix n n R} (hA : continuous A) :
  continuous (λ x, (A x).det) :=
begin
  simp_rw matrix.det_apply,
  refine continuous_finset_sum _ (λ l _, continuous.const_smul _ _),
  refine continuous_finset_prod _ (λ l _, hA.matrix_elem _ _),
end

@[continuity]
lemma continuous.matrix_update_column [decidable_eq n] (i : n)
  {A : X → matrix m n R} {B : X → m → R} (hA : continuous A) (hB : continuous B) :
  continuous (λ x, (A x).update_column i (B x)) :=
continuous_matrix $ λ j k, (continuous_apply k).comp $
  ((continuous_apply _).comp hA).update i ((continuous_apply _).comp hB)

@[continuity]
lemma continuous.matrix_update_row [decidable_eq m] (i : m)
  {A : X → matrix m n R} {B : X → n → R} (hA : continuous A) (hB : continuous B) :
  continuous (λ x, (A x).update_row i (B x)) :=
hA.update i hB

@[continuity]
lemma continuous.matrix_cramer [fintype n] [decidable_eq n] [comm_ring R] [topological_ring R]
  {A : X → matrix n n R} {B : X → n → R} (hA : continuous A) (hB : continuous B) :
  continuous (λ x, (A x).cramer (B x)) :=
continuous_pi $ λ i, (hA.matrix_update_column _ hB).matrix_det

@[continuity]
lemma continuous.matrix_adjugate [fintype n] [decidable_eq n] [comm_ring R] [topological_ring R]
  {A : X → matrix n n R} (hA : continuous A) :
  continuous (λ x, (A x).adjugate) :=
continuous_matrix $ λ j k, (hA.matrix_transpose.matrix_update_column k continuous_const).matrix_det

/-- When `ring.inverse` is continuous at the determinant (such as in a `normed_ring`, or a
`topological_field`), so is `matrix.has_inv`. -/
lemma continuous_at_matrix_inv [fintype n] [decidable_eq n] [comm_ring R] [topological_ring R]
  (A : matrix n n R) (h : continuous_at ring.inverse A.det) :
  continuous_at has_inv.inv A :=
(h.comp continuous_id.matrix_det.continuous_at).smul continuous_id.matrix_adjugate.continuous_at

-- lemmas about functions in `data/matrix/block.lean`
section block_matrices

@[continuity]
lemma continuous.matrix_from_blocks
  {A : X → matrix n l R} {B : X → matrix n m R} {C : X → matrix p l R} {D : X → matrix p m R}
  (hA : continuous A) (hB : continuous B) (hC : continuous C) (hD : continuous D) :
  continuous (λ x, matrix.from_blocks (A x) (B x) (C x) (D x)) :=
continuous_matrix $ λ i j,
  by cases i; cases j; refine continuous.matrix_elem _ i j; assumption

@[continuity]
lemma continuous.matrix_block_diagonal [has_zero R] [decidable_eq p] {A : X → p → matrix m n R}
  (hA : continuous A) :
  continuous (λ x, block_diagonal (A x)) :=
continuous_matrix $ λ ⟨i₁, i₂⟩ ⟨j₁, j₂⟩,
  (((continuous_apply i₂).comp hA).matrix_elem i₁ j₁).if_const _ continuous_zero

@[continuity]
lemma continuous.matrix_block_diag {A : X → matrix (m × p) (n × p) R} (hA : continuous A) :
  continuous (λ x, block_diag (A x)) :=
continuous_pi $ λ i, continuous_matrix $ λ j k, hA.matrix_elem _ _

@[continuity]
lemma continuous.matrix_block_diagonal' [has_zero R] [decidable_eq l]
  {A : X → Π i, matrix (m' i) (n' i) R} (hA : continuous A) :
  continuous (λ x, block_diagonal' (A x)) :=
continuous_matrix $ λ ⟨i₁, i₂⟩ ⟨j₁, j₂⟩, begin
  dsimp only [block_diagonal'],
  split_ifs,
  { subst h,
    exact ((continuous_apply i₁).comp hA).matrix_elem i₂ j₂ },
  { exact continuous_const },
end

@[continuity]
lemma continuous.matrix_block_diag' {A : X → matrix (Σ i, m' i) (Σ i, n' i) R} (hA : continuous A) :
  continuous (λ x, block_diag' (A x)) :=
continuous_pi $ λ i, continuous_matrix $ λ j k, hA.matrix_elem _ _

end block_matrices

end continuity

/-! ### Lemmas about infinite sums -/
section tsum
variables [semiring α] [add_comm_monoid R] [topological_space R] [module α R]

lemma has_sum.matrix_transpose {f : X → matrix m n R} {a : matrix m n R} (hf : has_sum f a) :
  has_sum (λ x, (f x)ᵀ) aᵀ :=
(hf.map (@matrix.transpose_add_equiv m n R _) continuous_id.matrix_transpose : _)

lemma summable.matrix_transpose {f : X → matrix m n R} (hf : summable f) :
  summable (λ x, (f x)ᵀ) :=
hf.has_sum.matrix_transpose.summable

@[simp] lemma summable_matrix_transpose {f : X → matrix m n R} :
  summable (λ x, (f x)ᵀ) ↔ summable f :=
(summable.map_iff_of_equiv (@matrix.transpose_add_equiv m n R _)
    (@continuous_id (matrix m n R) _).matrix_transpose (continuous_id.matrix_transpose) : _)

lemma matrix.transpose_tsum [t2_space R] {f : X → matrix m n R} : (∑' x, f x)ᵀ = ∑' x, (f x)ᵀ :=
begin
  by_cases hf : summable f,
  { exact hf.has_sum.matrix_transpose.tsum_eq.symm },
  { have hft := summable_matrix_transpose.not.mpr hf,
    rw [tsum_eq_zero_of_not_summable hf, tsum_eq_zero_of_not_summable hft, transpose_zero] },
end

lemma has_sum.matrix_conj_transpose [star_add_monoid R] [has_continuous_star R]
  {f : X → matrix m n R} {a : matrix m n R} (hf : has_sum f a) :
  has_sum (λ x, (f x)ᴴ) aᴴ :=
(hf.map (@matrix.conj_transpose_add_equiv m n R _ _) continuous_id.matrix_conj_transpose : _)

lemma summable.matrix_conj_transpose [star_add_monoid R] [has_continuous_star R]
  {f : X → matrix m n R} (hf : summable f) :
  summable (λ x, (f x)ᴴ) :=
hf.has_sum.matrix_conj_transpose.summable

@[simp] lemma summable_matrix_conj_transpose [star_add_monoid R] [has_continuous_star R]
  {f : X → matrix m n R} :
  summable (λ x, (f x)ᴴ) ↔ summable f :=
(summable.map_iff_of_equiv (@matrix.conj_transpose_add_equiv m n R _ _)
  (@continuous_id (matrix m n R) _).matrix_conj_transpose (continuous_id.matrix_conj_transpose) : _)

lemma matrix.conj_transpose_tsum [star_add_monoid R] [has_continuous_star R] [t2_space R]
  {f : X → matrix m n R} : (∑' x, f x)ᴴ = ∑' x, (f x)ᴴ :=
begin
  by_cases hf : summable f,
  { exact hf.has_sum.matrix_conj_transpose.tsum_eq.symm },
  { have hft := summable_matrix_conj_transpose.not.mpr hf,
    rw [tsum_eq_zero_of_not_summable hf, tsum_eq_zero_of_not_summable hft, conj_transpose_zero] },
end

lemma has_sum.matrix_diagonal [decidable_eq n] {f : X → n → R} {a : n → R} (hf : has_sum f a) :
  has_sum (λ x, diagonal (f x)) (diagonal a) :=
(hf.map (diagonal_add_monoid_hom n R) $ continuous.matrix_diagonal $ by exact continuous_id : _)

lemma summable.matrix_diagonal [decidable_eq n] {f : X → n → R} (hf : summable f) :
  summable (λ x, diagonal (f x)) :=
hf.has_sum.matrix_diagonal.summable

@[simp] lemma summable_matrix_diagonal [decidable_eq n] {f : X → n → R} :
  summable (λ x, diagonal (f x)) ↔ summable f :=
(summable.map_iff_of_left_inverse
  (@matrix.diagonal_add_monoid_hom n R _ _) (matrix.diag_add_monoid_hom n R)
  (by exact continuous.matrix_diagonal continuous_id)
  continuous_matrix_diag
  (λ A, diag_diagonal A) : _)

lemma matrix.diagonal_tsum [decidable_eq n] [t2_space R] {f : X → n → R} :
  diagonal (∑' x, f x) = ∑' x, diagonal (f x) :=
begin
  by_cases hf : summable f,
  { exact hf.has_sum.matrix_diagonal.tsum_eq.symm },
  { have hft := summable_matrix_diagonal.not.mpr hf,
    rw [tsum_eq_zero_of_not_summable hf, tsum_eq_zero_of_not_summable hft],
    exact diagonal_zero },
end

lemma has_sum.matrix_diag {f : X → matrix n n R} {a : matrix n n R} (hf : has_sum f a) :
  has_sum (λ x, diag (f x)) (diag a) :=
(hf.map (diag_add_monoid_hom n R) continuous_matrix_diag : _)

lemma summable.matrix_diag {f : X → matrix n n R} (hf : summable f) : summable (λ x, diag (f x)) :=
hf.has_sum.matrix_diag.summable

section block_matrices

lemma has_sum.matrix_block_diagonal [decidable_eq p]
  {f : X → p → matrix m n R} {a : p → matrix m n R} (hf : has_sum f a) :
  has_sum (λ x, block_diagonal (f x)) (block_diagonal a) :=
(hf.map (block_diagonal_add_monoid_hom m n p R) $
  continuous.matrix_block_diagonal $ by exact continuous_id : _)

lemma summable.matrix_block_diagonal [decidable_eq p] {f : X → p → matrix m n R} (hf : summable f) :
  summable (λ x, block_diagonal (f x)) :=
hf.has_sum.matrix_block_diagonal.summable

lemma summable_matrix_block_diagonal [decidable_eq p] {f : X → p → matrix m n R} :
  summable (λ x, block_diagonal (f x)) ↔ summable f :=
(summable.map_iff_of_left_inverse
  (matrix.block_diagonal_add_monoid_hom m n p R) (matrix.block_diag_add_monoid_hom m n p R)
  (by exact continuous.matrix_block_diagonal continuous_id)
  (by exact continuous.matrix_block_diag continuous_id)
  (λ A, block_diag_block_diagonal A) : _)

lemma matrix.block_diagonal_tsum [decidable_eq p] [t2_space R] {f : X → p → matrix m n R} :
  block_diagonal (∑' x, f x) = ∑' x, block_diagonal (f x) :=
begin
  by_cases hf : summable f,
  { exact hf.has_sum.matrix_block_diagonal.tsum_eq.symm },
  { have hft := summable_matrix_block_diagonal.not.mpr hf,
    rw [tsum_eq_zero_of_not_summable hf, tsum_eq_zero_of_not_summable hft],
    exact block_diagonal_zero },
end

lemma has_sum.matrix_block_diag {f : X → matrix (m × p) (n × p) R}
  {a : matrix (m × p) (n × p) R} (hf : has_sum f a) :
  has_sum (λ x, block_diag (f x)) (block_diag a) :=
(hf.map (block_diag_add_monoid_hom m n p R) $ continuous.matrix_block_diag continuous_id : _)

lemma summable.matrix_block_diag {f : X → matrix (m × p) (n × p) R} (hf : summable f) :
  summable (λ x, block_diag (f x)) :=
hf.has_sum.matrix_block_diag.summable

lemma has_sum.matrix_block_diagonal' [decidable_eq l]
  {f : X → Π i, matrix (m' i) (n' i) R} {a : Π i, matrix (m' i) (n' i) R} (hf : has_sum f a) :
  has_sum (λ x, block_diagonal' (f x)) (block_diagonal' a) :=
(hf.map (block_diagonal'_add_monoid_hom m' n' R) $
  continuous.matrix_block_diagonal' $ by exact continuous_id : _)

lemma summable.matrix_block_diagonal' [decidable_eq l]
  {f : X → Π i, matrix (m' i) (n' i) R} (hf : summable f) :
  summable (λ x, block_diagonal' (f x)) :=
hf.has_sum.matrix_block_diagonal'.summable

lemma summable_matrix_block_diagonal' [decidable_eq l] {f : X → Π i, matrix (m' i) (n' i) R} :
  summable (λ x, block_diagonal' (f x)) ↔ summable f :=
(summable.map_iff_of_left_inverse
  (matrix.block_diagonal'_add_monoid_hom m' n' R) (matrix.block_diag'_add_monoid_hom m' n' R)
  (by exact continuous.matrix_block_diagonal' continuous_id)
  (by exact continuous.matrix_block_diag' continuous_id)
  (λ A, block_diag'_block_diagonal' A) : _)

lemma matrix.block_diagonal'_tsum [decidable_eq l] [t2_space R]
  {f : X → Π i, matrix (m' i) (n' i) R} :
  block_diagonal' (∑' x, f x) = ∑' x, block_diagonal' (f x) :=
begin
  by_cases hf : summable f,
  { exact hf.has_sum.matrix_block_diagonal'.tsum_eq.symm },
  { have hft := summable_matrix_block_diagonal'.not.mpr hf,
    rw [tsum_eq_zero_of_not_summable hf, tsum_eq_zero_of_not_summable hft],
    exact block_diagonal'_zero },
end

lemma has_sum.matrix_block_diag' {f : X → matrix (Σ i, m' i) (Σ i, n' i) R}
  {a : matrix (Σ i, m' i) (Σ i, n' i) R} (hf : has_sum f a) :
  has_sum (λ x, block_diag' (f x)) (block_diag' a) :=
(hf.map (block_diag'_add_monoid_hom m' n' R) $ continuous.matrix_block_diag' continuous_id : _)

lemma summable.matrix_block_diag' {f : X → matrix (Σ i, m' i) (Σ i, n' i) R} (hf : summable f) :
  summable (λ x, block_diag' (f x)) :=
hf.has_sum.matrix_block_diag'.summable

end block_matrices

end tsum