/-
Copyright (c) 2019 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel, Floris van Doorn

! This file was ported from Lean 3 source module analysis.calculus.cont_diff
! leanprover-community/mathlib commit 91862a6001a8b6ae3f261cdd8eea42f6ac596886
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.Calculus.ContDiffDef
import Mathbin.Analysis.Calculus.MeanValue
import Mathbin.Analysis.NormedSpace.FiniteDimension

/-!
# Higher differentiability of usual operations

We prove that the usual operations (addition, multiplication, difference, composition, and
so on) preserve `C^n` functions. We also expand the API aound `C^n` functions.

## Notations

We use the notation `E [×n]→L[𝕜] F` for the space of continuous multilinear maps on `E^n` with
values in `F`. This is the space in which the `n`-th derivative of a function from `E` to `F` lives.

In this file, we denote `⊤ : ℕ∞` with `∞`.

## Tags

derivative, differentiability, higher derivative, `C^n`, multilinear, Taylor series, formal series
-/

noncomputable section

open Classical BigOperators NNReal

-- mathport name: «expr∞»
local notation "∞" => (⊤ : ℕ∞)

universe u v w uD uE uF uG

attribute [local instance]
  NormedAddCommGroup.toAddCommGroup NormedSpace.toModule' AddCommGroup.toAddCommMonoid

namespace Finset

-- TODO porting note: move the next two lemmas to the file `data.nat.choose.sum`
/-- The sum of `(n+1).choose i * f i (n+1-i)` can be split into two sums at rank `n`,
respectively of `n.choose i * f i (n+1-i)` and `n.choose i * f (i+1) (n-i)`. -/
theorem sum_choose_succ_mul {R : Type _} [Semiring R] (f : ℕ → ℕ → R) (n : ℕ) :
    (∑ i in range (n + 2), ((n + 1).choose i : R) * f i (n + 1 - i)) =
      (∑ i in range (n + 1), (n.choose i : R) * f i (n + 1 - i)) +
        ∑ i in range (n + 1), (n.choose i : R) * f (i + 1) (n - i) :=
  by
  have A :
    (∑ i in range (n + 1), (n.choose (i + 1) : R) * f (i + 1) (n - i)) + f 0 (n + 1) =
      ∑ i in range (n + 1), n.choose i * f i (n + 1 - i) :=
    by
    rw [Finset.sum_range_succ, Finset.sum_range_succ']
    simp only [Nat.choose_succ_self, algebraMap.coe_zero, MulZeroClass.zero_mul, add_zero,
      Nat.succ_sub_succ_eq_sub, Nat.choose_zero_right, algebraMap.coe_one, one_mul, tsub_zero]
  calc
    (∑ i in Finset.range (n + 2), ((n + 1).choose i : R) * f i (n + 1 - i)) =
        (∑ i in Finset.range (n + 1), ((n + 1).choose (i + 1) : R) * f (i + 1) (n + 1 - (i + 1))) +
          f 0 (n + 1 - 0) :=
      by
      rw [Finset.sum_range_succ']
      simp only [Nat.choose_zero_right, algebraMap.coe_one, one_mul]
    _ =
        (∑ i in Finset.range (n + 1), (n.choose i : R) * f i (n + 1 - i)) +
          ∑ i in Finset.range (n + 1), n.choose i * f (i + 1) (n - i) :=
      by
      simp only [Nat.choose_succ_succ, Nat.cast_add, Nat.succ_sub_succ_eq_sub, tsub_zero, add_mul]
      rw [Finset.sum_add_distrib, ← A]
      abel

#align finset.sum_choose_succ_mul Finset.sum_choose_succ_mul

/-- The sum along the antidiagonal of `(n+1).choose i * f i j` can be split into two sums along the
antidiagonal at rank `n`, respectively of `n.choose i * f i (j+1)` and `n.choose j * f (i+1) j`. -/
theorem sum_antidiagonal_choose_succ_mul {R : Type _} [Semiring R] (f : ℕ → ℕ → R) (n : ℕ) :
    (∑ ij in Nat.antidiagonal (n + 1), ((n + 1).choose ij.1 : R) * f ij.1 ij.2) =
      (∑ ij in Nat.antidiagonal n, (n.choose ij.1 : R) * f ij.1 (ij.2 + 1)) +
        ∑ ij in Nat.antidiagonal n, (n.choose ij.2 : R) * f (ij.1 + 1) ij.2 :=
  by
  convert sum_choose_succ_mul f n using 1
  · exact nat.sum_antidiagonal_eq_sum_range_succ (fun i j => ((n + 1).choose i : R) * f i j) (n + 1)
  congr 1
  · rw [nat.sum_antidiagonal_eq_sum_range_succ (fun i j => (n.choose i : R) * f i (j + 1)) n]
    apply Finset.sum_congr rfl fun i hi => _
    have : n + 1 - i = n - i + 1 := Nat.sub_add_comm (Nat.lt_succ_iff.1 (Finset.mem_range.1 hi))
    simp only [this]
  · suffices H :
      (∑ ij in nat.antidiagonal n, (n.choose ij.2 : R) * f (ij.1 + 1) ij.2) =
        ∑ ij in nat.antidiagonal n, (n.choose ij.1 : R) * f (ij.1 + 1) ij.2
    · rw [H, nat.sum_antidiagonal_eq_sum_range_succ (fun i j => (n.choose i : R) * f (i + 1) j) n]
    apply Finset.sum_congr rfl fun i hi => _
    congr 2
    apply Nat.choose_symm_of_eq_add
    rw [← nat.mem_antidiagonal.1 hi, add_comm]
#align finset.sum_antidiagonal_choose_succ_mul Finset.sum_antidiagonal_choose_succ_mul

end Finset

open Set Fin Filter Function

open Topology

variable {𝕜 : Type _} [NontriviallyNormedField 𝕜] {D : Type uD} [NormedAddCommGroup D]
  [NormedSpace 𝕜 D] {E : Type uE} [NormedAddCommGroup E] [NormedSpace 𝕜 E] {F : Type uF}
  [NormedAddCommGroup F] [NormedSpace 𝕜 F] {G : Type uG} [NormedAddCommGroup G] [NormedSpace 𝕜 G]
  {X : Type _} [NormedAddCommGroup X] [NormedSpace 𝕜 X] {s s₁ t u : Set E} {f f₁ : E → F}
  {g : F → G} {x x₀ : E} {c : F} {b : E × F → G} {m n : ℕ∞} {p : E → FormalMultilinearSeries 𝕜 E F}

/-! ### Constants -/

@[simp]
theorem iteratedFderiv_zero_fun {n : ℕ} : (iteratedFderiv 𝕜 n fun x : E => (0 : F)) = 0 :=
  by
  induction' n with n IH
  · ext m
    simp
  · ext (x m)
    rw [iteratedFderiv_succ_apply_left, IH]
    change (fderiv 𝕜 (fun x : E => (0 : E[×n]→L[𝕜] F)) x : E → E[×n]→L[𝕜] F) (m 0) (tail m) = _
    rw [fderiv_const]
    rfl
#align iterated_fderiv_zero_fun iteratedFderiv_zero_fun

theorem contDiff_zero_fun : ContDiff 𝕜 n fun x : E => (0 : F) :=
  by
  apply contDiff_of_differentiable_iteratedFderiv fun m hm => _
  rw [iteratedFderiv_zero_fun]
  exact differentiable_const (0 : E[×m]→L[𝕜] F)
#align cont_diff_zero_fun contDiff_zero_fun

/-- Constants are `C^∞`.
-/
theorem contDiff_const {c : F} : ContDiff 𝕜 n fun x : E => c :=
  by
  suffices h : ContDiff 𝕜 ∞ fun x : E => c; · exact h.of_le le_top
  rw [contDiff_top_iff_fderiv]
  refine' ⟨differentiable_const c, _⟩
  rw [fderiv_const]
  exact contDiff_zero_fun
#align cont_diff_const contDiff_const

theorem contDiffOn_const {c : F} {s : Set E} : ContDiffOn 𝕜 n (fun x : E => c) s :=
  contDiff_const.ContDiffOn
#align cont_diff_on_const contDiffOn_const

theorem contDiffAt_const {c : F} : ContDiffAt 𝕜 n (fun x : E => c) x :=
  contDiff_const.ContDiffAt
#align cont_diff_at_const contDiffAt_const

theorem contDiffWithinAt_const {c : F} : ContDiffWithinAt 𝕜 n (fun x : E => c) s x :=
  contDiffAt_const.ContDiffWithinAt
#align cont_diff_within_at_const contDiffWithinAt_const

@[nontriviality]
theorem contDiff_of_subsingleton [Subsingleton F] : ContDiff 𝕜 n f :=
  by
  rw [Subsingleton.elim f fun _ => 0]
  exact contDiff_const
#align cont_diff_of_subsingleton contDiff_of_subsingleton

@[nontriviality]
theorem contDiffAt_of_subsingleton [Subsingleton F] : ContDiffAt 𝕜 n f x :=
  by
  rw [Subsingleton.elim f fun _ => 0]
  exact contDiffAt_const
#align cont_diff_at_of_subsingleton contDiffAt_of_subsingleton

@[nontriviality]
theorem contDiffWithinAt_of_subsingleton [Subsingleton F] : ContDiffWithinAt 𝕜 n f s x :=
  by
  rw [Subsingleton.elim f fun _ => 0]
  exact contDiffWithinAt_const
#align cont_diff_within_at_of_subsingleton contDiffWithinAt_of_subsingleton

@[nontriviality]
theorem contDiffOn_of_subsingleton [Subsingleton F] : ContDiffOn 𝕜 n f s :=
  by
  rw [Subsingleton.elim f fun _ => 0]
  exact contDiffOn_const
#align cont_diff_on_of_subsingleton contDiffOn_of_subsingleton

/-! ### Smoothness of linear functions -/

/-- Unbundled bounded linear functions are `C^∞`.
-/
theorem IsBoundedLinearMap.contDiff (hf : IsBoundedLinearMap 𝕜 f) : ContDiff 𝕜 n f :=
  by
  suffices h : ContDiff 𝕜 ∞ f; · exact h.of_le le_top
  rw [contDiff_top_iff_fderiv]
  refine' ⟨hf.differentiable, _⟩
  simp_rw [hf.fderiv]
  exact contDiff_const
#align is_bounded_linear_map.cont_diff IsBoundedLinearMap.contDiff

theorem ContinuousLinearMap.contDiff (f : E →L[𝕜] F) : ContDiff 𝕜 n f :=
  f.IsBoundedLinearMap.ContDiff
#align continuous_linear_map.cont_diff ContinuousLinearMap.contDiff

theorem ContinuousLinearEquiv.contDiff (f : E ≃L[𝕜] F) : ContDiff 𝕜 n f :=
  (f : E →L[𝕜] F).ContDiff
#align continuous_linear_equiv.cont_diff ContinuousLinearEquiv.contDiff

theorem LinearIsometry.contDiff (f : E →ₗᵢ[𝕜] F) : ContDiff 𝕜 n f :=
  f.toContinuousLinearMap.ContDiff
#align linear_isometry.cont_diff LinearIsometry.contDiff

theorem LinearIsometryEquiv.contDiff (f : E ≃ₗᵢ[𝕜] F) : ContDiff 𝕜 n f :=
  (f : E →L[𝕜] F).ContDiff
#align linear_isometry_equiv.cont_diff LinearIsometryEquiv.contDiff

/-- The identity is `C^∞`.
-/
theorem contDiff_id : ContDiff 𝕜 n (id : E → E) :=
  IsBoundedLinearMap.id.ContDiff
#align cont_diff_id contDiff_id

theorem contDiffWithinAt_id {s x} : ContDiffWithinAt 𝕜 n (id : E → E) s x :=
  contDiff_id.ContDiffWithinAt
#align cont_diff_within_at_id contDiffWithinAt_id

theorem contDiffAt_id {x} : ContDiffAt 𝕜 n (id : E → E) x :=
  contDiff_id.ContDiffAt
#align cont_diff_at_id contDiffAt_id

theorem contDiffOn_id {s} : ContDiffOn 𝕜 n (id : E → E) s :=
  contDiff_id.ContDiffOn
#align cont_diff_on_id contDiffOn_id

/-- Bilinear functions are `C^∞`.
-/
theorem IsBoundedBilinearMap.contDiff (hb : IsBoundedBilinearMap 𝕜 b) : ContDiff 𝕜 n b :=
  by
  suffices h : ContDiff 𝕜 ∞ b; · exact h.of_le le_top
  rw [contDiff_top_iff_fderiv]
  refine' ⟨hb.differentiable, _⟩
  simp [hb.fderiv]
  exact hb.is_bounded_linear_map_deriv.cont_diff
#align is_bounded_bilinear_map.cont_diff IsBoundedBilinearMap.contDiff

/-- If `f` admits a Taylor series `p` in a set `s`, and `g` is linear, then `g ∘ f` admits a Taylor
series whose `k`-th term is given by `g ∘ (p k)`. -/
theorem HasFtaylorSeriesUpToOn.continuousLinearMapComp (g : F →L[𝕜] G)
    (hf : HasFtaylorSeriesUpToOn n f p s) :
    HasFtaylorSeriesUpToOn n (g ∘ f) (fun x k => g.compContinuousMultilinearMap (p x k)) s :=
  by
  set L : ∀ m : ℕ, (E[×m]→L[𝕜] F) →L[𝕜] E[×m]→L[𝕜] G := fun m =>
    ContinuousLinearMap.compContinuousMultilinearMapL 𝕜 (fun _ => E) F G g
  constructor
  · exact fun x hx => congr_arg g (hf.zero_eq x hx)
  · intro m hm x hx
    convert(L m).HasFderivAt.comp_hasFderivWithinAt x (hf.fderiv_within m hm x hx)
  · intro m hm
    convert(L m).Continuous.comp_continuousOn (hf.cont m hm)
#align has_ftaylor_series_up_to_on.continuous_linear_map_comp HasFtaylorSeriesUpToOn.continuousLinearMapComp

/-- Composition by continuous linear maps on the left preserves `C^n` functions in a domain
at a point. -/
theorem ContDiffWithinAt.continuousLinearMap_comp (g : F →L[𝕜] G)
    (hf : ContDiffWithinAt 𝕜 n f s x) : ContDiffWithinAt 𝕜 n (g ∘ f) s x :=
  by
  intro m hm
  rcases hf m hm with ⟨u, hu, p, hp⟩
  exact ⟨u, hu, _, hp.continuous_linear_map_comp g⟩
#align cont_diff_within_at.continuous_linear_map_comp ContDiffWithinAt.continuousLinearMap_comp

/-- Composition by continuous linear maps on the left preserves `C^n` functions in a domain
at a point. -/
theorem ContDiffAt.continuousLinearMap_comp (g : F →L[𝕜] G) (hf : ContDiffAt 𝕜 n f x) :
    ContDiffAt 𝕜 n (g ∘ f) x :=
  ContDiffWithinAt.continuousLinearMap_comp g hf
#align cont_diff_at.continuous_linear_map_comp ContDiffAt.continuousLinearMap_comp

/-- Composition by continuous linear maps on the left preserves `C^n` functions on domains. -/
theorem ContDiffOn.continuousLinearMap_comp (g : F →L[𝕜] G) (hf : ContDiffOn 𝕜 n f s) :
    ContDiffOn 𝕜 n (g ∘ f) s := fun x hx => (hf x hx).continuousLinearMapComp g
#align cont_diff_on.continuous_linear_map_comp ContDiffOn.continuousLinearMap_comp

/-- Composition by continuous linear maps on the left preserves `C^n` functions. -/
theorem ContDiff.continuousLinearMap_comp {f : E → F} (g : F →L[𝕜] G) (hf : ContDiff 𝕜 n f) :
    ContDiff 𝕜 n fun x => g (f x) :=
  contDiffOn_univ.1 <| ContDiffOn.continuousLinearMap_comp _ (contDiffOn_univ.2 hf)
#align cont_diff.continuous_linear_map_comp ContDiff.continuousLinearMap_comp

/-- The iterated derivative within a set of the composition with a linear map on the left is
obtained by applying the linear map to the iterated derivative. -/
theorem ContinuousLinearMap.iteratedFderivWithin_comp_left {f : E → F} (g : F →L[𝕜] G)
    (hf : ContDiffOn 𝕜 n f s) (hs : UniqueDiffOn 𝕜 s) (hx : x ∈ s) {i : ℕ}
    (hi : (i : WithTop ℕ) ≤ n) :
    iteratedFderivWithin 𝕜 i (g ∘ f) s x =
      g.compContinuousMultilinearMap (iteratedFderivWithin 𝕜 i f s x) :=
  (((hf.ftaylorSeriesWithin hs).continuousLinearMapComp g).eq_ftaylor_series_of_uniqueDiffOn hi hs
      hx).symm
#align continuous_linear_map.iterated_fderiv_within_comp_left ContinuousLinearMap.iteratedFderivWithin_comp_left

/-- The iterated derivative of the composition with a linear map on the left is
obtained by applying the linear map to the iterated derivative. -/
theorem ContinuousLinearMap.iteratedFderiv_comp_left {f : E → F} (g : F →L[𝕜] G)
    (hf : ContDiff 𝕜 n f) (x : E) {i : ℕ} (hi : (i : WithTop ℕ) ≤ n) :
    iteratedFderiv 𝕜 i (g ∘ f) x = g.compContinuousMultilinearMap (iteratedFderiv 𝕜 i f x) :=
  by
  simp only [← iteratedFderivWithin_univ]
  exact g.iterated_fderiv_within_comp_left hf.cont_diff_on uniqueDiffOn_univ (mem_univ x) hi
#align continuous_linear_map.iterated_fderiv_comp_left ContinuousLinearMap.iteratedFderiv_comp_left

/-- The iterated derivative within a set of the composition with a linear equiv on the left is
obtained by applying the linear equiv to the iterated derivative. This is true without
differentiability assumptions. -/
theorem ContinuousLinearEquiv.iteratedFderivWithin_comp_left (g : F ≃L[𝕜] G) (f : E → F)
    (hs : UniqueDiffOn 𝕜 s) (hx : x ∈ s) (i : ℕ) :
    iteratedFderivWithin 𝕜 i (g ∘ f) s x =
      (g : F →L[𝕜] G).compContinuousMultilinearMap (iteratedFderivWithin 𝕜 i f s x) :=
  by
  induction' i with i IH generalizing x
  · ext1 m
    simp only [iteratedFderivWithin_zero_apply, ContinuousLinearEquiv.coe_coe,
      ContinuousLinearMap.compContinuousMultilinearMap_coe, EmbeddingLike.apply_eq_iff_eq]
  · ext1 m
    rw [iteratedFderivWithin_succ_apply_left]
    have Z :
      fderivWithin 𝕜 (iteratedFderivWithin 𝕜 i (g ∘ f) s) s x =
        fderivWithin 𝕜
          (fun y =>
            g.comp_continuous_multilinear_mapL (fun j : Fin i => E)
              (iteratedFderivWithin 𝕜 i f s y))
          s x :=
      fderivWithin_congr' (hs x hx) (fun y hy => IH hy) hx
    simp_rw [Z]
    rw [(g.comp_continuous_multilinear_mapL fun j : Fin i => E).comp_fderivWithin (hs x hx)]
    simp only [ContinuousLinearMap.coe_comp', ContinuousLinearEquiv.coe_coe, comp_app,
      ContinuousLinearEquiv.compContinuousMultilinearMapL_apply,
      ContinuousLinearMap.compContinuousMultilinearMap_coe, EmbeddingLike.apply_eq_iff_eq]
    rw [iteratedFderivWithin_succ_apply_left]
#align continuous_linear_equiv.iterated_fderiv_within_comp_left ContinuousLinearEquiv.iteratedFderivWithin_comp_left

/-- Composition with a linear isometry on the left preserves the norm of the iterated
derivative within a set. -/
theorem LinearIsometry.norm_iteratedFderivWithin_comp_left {f : E → F} (g : F →ₗᵢ[𝕜] G)
    (hf : ContDiffOn 𝕜 n f s) (hs : UniqueDiffOn 𝕜 s) (hx : x ∈ s) {i : ℕ}
    (hi : (i : WithTop ℕ) ≤ n) :
    ‖iteratedFderivWithin 𝕜 i (g ∘ f) s x‖ = ‖iteratedFderivWithin 𝕜 i f s x‖ :=
  by
  have :
    iteratedFderivWithin 𝕜 i (g ∘ f) s x =
      g.to_continuous_linear_map.comp_continuous_multilinear_map (iteratedFderivWithin 𝕜 i f s x) :=
    g.to_continuous_linear_map.iterated_fderiv_within_comp_left hf hs hx hi
  rw [this]
  apply LinearIsometry.norm_compContinuousMultilinearMap
#align linear_isometry.norm_iterated_fderiv_within_comp_left LinearIsometry.norm_iteratedFderivWithin_comp_left

/-- Composition with a linear isometry on the left preserves the norm of the iterated
derivative. -/
theorem LinearIsometry.norm_iteratedFderiv_comp_left {f : E → F} (g : F →ₗᵢ[𝕜] G)
    (hf : ContDiff 𝕜 n f) (x : E) {i : ℕ} (hi : (i : WithTop ℕ) ≤ n) :
    ‖iteratedFderiv 𝕜 i (g ∘ f) x‖ = ‖iteratedFderiv 𝕜 i f x‖ :=
  by
  simp only [← iteratedFderivWithin_univ]
  exact g.norm_iterated_fderiv_within_comp_left hf.cont_diff_on uniqueDiffOn_univ (mem_univ x) hi
#align linear_isometry.norm_iterated_fderiv_comp_left LinearIsometry.norm_iteratedFderiv_comp_left

/-- Composition with a linear isometry equiv on the left preserves the norm of the iterated
derivative within a set. -/
theorem LinearIsometryEquiv.norm_iteratedFderivWithin_comp_left (g : F ≃ₗᵢ[𝕜] G) (f : E → F)
    (hs : UniqueDiffOn 𝕜 s) (hx : x ∈ s) (i : ℕ) :
    ‖iteratedFderivWithin 𝕜 i (g ∘ f) s x‖ = ‖iteratedFderivWithin 𝕜 i f s x‖ :=
  by
  have :
    iteratedFderivWithin 𝕜 i (g ∘ f) s x =
      (g : F →L[𝕜] G).compContinuousMultilinearMap (iteratedFderivWithin 𝕜 i f s x) :=
    g.to_continuous_linear_equiv.iterated_fderiv_within_comp_left f hs hx i
  rw [this]
  apply LinearIsometry.norm_compContinuousMultilinearMap g.to_linear_isometry
#align linear_isometry_equiv.norm_iterated_fderiv_within_comp_left LinearIsometryEquiv.norm_iteratedFderivWithin_comp_left

/-- Composition with a linear isometry equiv on the left preserves the norm of the iterated
derivative. -/
theorem LinearIsometryEquiv.norm_iteratedFderiv_comp_left (g : F ≃ₗᵢ[𝕜] G) (f : E → F) (x : E)
    (i : ℕ) : ‖iteratedFderiv 𝕜 i (g ∘ f) x‖ = ‖iteratedFderiv 𝕜 i f x‖ :=
  by
  rw [← iteratedFderivWithin_univ, ← iteratedFderivWithin_univ]
  apply g.norm_iterated_fderiv_within_comp_left f uniqueDiffOn_univ (mem_univ x) i
#align linear_isometry_equiv.norm_iterated_fderiv_comp_left LinearIsometryEquiv.norm_iteratedFderiv_comp_left

/-- Composition by continuous linear equivs on the left respects higher differentiability at a
point in a domain. -/
theorem ContinuousLinearEquiv.comp_contDiffWithinAt_iff (e : F ≃L[𝕜] G) :
    ContDiffWithinAt 𝕜 n (e ∘ f) s x ↔ ContDiffWithinAt 𝕜 n f s x :=
  ⟨fun H => by
    simpa only [(· ∘ ·), e.symm.coe_coe, e.symm_apply_apply] using
      H.continuous_linear_map_comp (e.symm : G →L[𝕜] F),
    fun H => H.continuousLinearMapComp (e : F →L[𝕜] G)⟩
#align continuous_linear_equiv.comp_cont_diff_within_at_iff ContinuousLinearEquiv.comp_contDiffWithinAt_iff

/-- Composition by continuous linear equivs on the left respects higher differentiability at a
point. -/
theorem ContinuousLinearEquiv.comp_contDiffAt_iff (e : F ≃L[𝕜] G) :
    ContDiffAt 𝕜 n (e ∘ f) x ↔ ContDiffAt 𝕜 n f x := by
  simp only [← contDiffWithinAt_univ, e.comp_cont_diff_within_at_iff]
#align continuous_linear_equiv.comp_cont_diff_at_iff ContinuousLinearEquiv.comp_contDiffAt_iff

/-- Composition by continuous linear equivs on the left respects higher differentiability on
domains. -/
theorem ContinuousLinearEquiv.comp_contDiffOn_iff (e : F ≃L[𝕜] G) :
    ContDiffOn 𝕜 n (e ∘ f) s ↔ ContDiffOn 𝕜 n f s := by
  simp [ContDiffOn, e.comp_cont_diff_within_at_iff]
#align continuous_linear_equiv.comp_cont_diff_on_iff ContinuousLinearEquiv.comp_contDiffOn_iff

/-- Composition by continuous linear equivs on the left respects higher differentiability. -/
theorem ContinuousLinearEquiv.comp_contDiff_iff (e : F ≃L[𝕜] G) :
    ContDiff 𝕜 n (e ∘ f) ↔ ContDiff 𝕜 n f := by
  simp only [← contDiffOn_univ, e.comp_cont_diff_on_iff]
#align continuous_linear_equiv.comp_cont_diff_iff ContinuousLinearEquiv.comp_contDiff_iff

/-- If `f` admits a Taylor series `p` in a set `s`, and `g` is linear, then `f ∘ g` admits a Taylor
series in `g ⁻¹' s`, whose `k`-th term is given by `p k (g v₁, ..., g vₖ)` . -/
theorem HasFtaylorSeriesUpToOn.compContinuousLinearMap (hf : HasFtaylorSeriesUpToOn n f p s)
    (g : G →L[𝕜] E) :
    HasFtaylorSeriesUpToOn n (f ∘ g) (fun x k => (p (g x) k).compContinuousLinearMap fun _ => g)
      (g ⁻¹' s) :=
  by
  let A : ∀ m : ℕ, (E[×m]→L[𝕜] F) → G[×m]→L[𝕜] F := fun m h => h.compContinuousLinearMap fun _ => g
  have hA : ∀ m, IsBoundedLinearMap 𝕜 (A m) := fun m =>
    isBoundedLinearMap_continuousMultilinearMap_comp_linear g
  constructor
  · intro x hx
    simp only [(hf.zero_eq (g x) hx).symm, Function.comp_apply]
    change (p (g x) 0 fun i : Fin 0 => g 0) = p (g x) 0 0
    rw [ContinuousLinearMap.map_zero]
    rfl
  · intro m hm x hx
    convert(hA m).HasFderivAt.comp_hasFderivWithinAt x
        ((hf.fderiv_within m hm (g x) hx).comp x g.has_fderiv_within_at (subset.refl _))
    ext (y v)
    change p (g x) (Nat.succ m) (g ∘ cons y v) = p (g x) m.succ (cons (g y) (g ∘ v))
    rw [comp_cons]
  · intro m hm
    exact
      (hA m).Continuous.comp_continuousOn
        ((hf.cont m hm).comp g.continuous.continuous_on (subset.refl _))
#align has_ftaylor_series_up_to_on.comp_continuous_linear_map HasFtaylorSeriesUpToOn.compContinuousLinearMap

/-- Composition by continuous linear maps on the right preserves `C^n` functions at a point on
a domain. -/
theorem ContDiffWithinAt.comp_continuousLinearMap {x : G} (g : G →L[𝕜] E)
    (hf : ContDiffWithinAt 𝕜 n f s (g x)) : ContDiffWithinAt 𝕜 n (f ∘ g) (g ⁻¹' s) x :=
  by
  intro m hm
  rcases hf m hm with ⟨u, hu, p, hp⟩
  refine' ⟨g ⁻¹' u, _, _, hp.comp_continuous_linear_map g⟩
  apply ContinuousWithinAt.preimage_mem_nhds_within'
  · exact g.continuous.continuous_within_at
  · apply nhdsWithin_mono (g x) _ hu
    rw [image_insert_eq]
    exact insert_subset_insert (image_preimage_subset g s)
#align cont_diff_within_at.comp_continuous_linear_map ContDiffWithinAt.comp_continuousLinearMap

/-- Composition by continuous linear maps on the right preserves `C^n` functions on domains. -/
theorem ContDiffOn.comp_continuousLinearMap (hf : ContDiffOn 𝕜 n f s) (g : G →L[𝕜] E) :
    ContDiffOn 𝕜 n (f ∘ g) (g ⁻¹' s) := fun x hx => (hf (g x) hx).compContinuousLinearMap g
#align cont_diff_on.comp_continuous_linear_map ContDiffOn.comp_continuousLinearMap

/-- Composition by continuous linear maps on the right preserves `C^n` functions. -/
theorem ContDiff.comp_continuousLinearMap {f : E → F} {g : G →L[𝕜] E} (hf : ContDiff 𝕜 n f) :
    ContDiff 𝕜 n (f ∘ g) :=
  contDiffOn_univ.1 <| ContDiffOn.comp_continuousLinearMap (contDiffOn_univ.2 hf) _
#align cont_diff.comp_continuous_linear_map ContDiff.comp_continuousLinearMap

/-- The iterated derivative within a set of the composition with a linear map on the right is
obtained by composing the iterated derivative with the linear map. -/
theorem ContinuousLinearMap.iteratedFderivWithin_comp_right {f : E → F} (g : G →L[𝕜] E)
    (hf : ContDiffOn 𝕜 n f s) (hs : UniqueDiffOn 𝕜 s) (h's : UniqueDiffOn 𝕜 (g ⁻¹' s)) {x : G}
    (hx : g x ∈ s) {i : ℕ} (hi : (i : WithTop ℕ) ≤ n) :
    iteratedFderivWithin 𝕜 i (f ∘ g) (g ⁻¹' s) x =
      (iteratedFderivWithin 𝕜 i f s (g x)).compContinuousLinearMap fun _ => g :=
  (((hf.ftaylorSeriesWithin hs).compContinuousLinearMap g).eq_ftaylor_series_of_uniqueDiffOn hi h's
      hx).symm
#align continuous_linear_map.iterated_fderiv_within_comp_right ContinuousLinearMap.iteratedFderivWithin_comp_right

/-- The iterated derivative within a set of the composition with a linear equiv on the right is
obtained by composing the iterated derivative with the linear equiv. -/
theorem ContinuousLinearEquiv.iteratedFderivWithin_comp_right (g : G ≃L[𝕜] E) (f : E → F)
    (hs : UniqueDiffOn 𝕜 s) {x : G} (hx : g x ∈ s) (i : ℕ) :
    iteratedFderivWithin 𝕜 i (f ∘ g) (g ⁻¹' s) x =
      (iteratedFderivWithin 𝕜 i f s (g x)).compContinuousLinearMap fun _ => g :=
  by
  induction' i with i IH generalizing x
  · ext1 m
    simp only [iteratedFderivWithin_zero_apply,
      ContinuousMultilinearMap.compContinuousLinearMap_apply]
  · ext1 m
    simp only [ContinuousMultilinearMap.compContinuousLinearMap_apply,
      ContinuousLinearEquiv.coe_coe, iteratedFderivWithin_succ_apply_left]
    have :
      fderivWithin 𝕜 (iteratedFderivWithin 𝕜 i (f ∘ ⇑g) (⇑g ⁻¹' s)) (⇑g ⁻¹' s) x =
        fderivWithin 𝕜
          (fun y =>
            ContinuousMultilinearMap.compContinuousLinearMapEquivL _ (fun _x : Fin i => g)
              (iteratedFderivWithin 𝕜 i f s (g y)))
          (g ⁻¹' s) x :=
      fderivWithin_congr' (g.unique_diff_on_preimage_iff.2 hs x hx) (fun y hy => IH hy) hx
    rw [this]
    rw [ContinuousLinearEquiv.comp_fderivWithin _ (g.unique_diff_on_preimage_iff.2 hs x hx)]
    simp only [ContinuousLinearMap.coe_comp', ContinuousLinearEquiv.coe_coe, comp_app,
      ContinuousMultilinearMap.compContinuousLinearMapEquivL_apply,
      ContinuousMultilinearMap.compContinuousLinearMap_apply]
    rw [ContinuousLinearEquiv.comp_right_fderivWithin _ (g.unique_diff_on_preimage_iff.2 hs x hx)]
    rfl
#align continuous_linear_equiv.iterated_fderiv_within_comp_right ContinuousLinearEquiv.iteratedFderivWithin_comp_right

/-- The iterated derivative of the composition with a linear map on the right is
obtained by composing the iterated derivative with the linear map. -/
theorem ContinuousLinearMap.iteratedFderiv_comp_right (g : G →L[𝕜] E) {f : E → F}
    (hf : ContDiff 𝕜 n f) (x : G) {i : ℕ} (hi : (i : WithTop ℕ) ≤ n) :
    iteratedFderiv 𝕜 i (f ∘ g) x =
      (iteratedFderiv 𝕜 i f (g x)).compContinuousLinearMap fun _ => g :=
  by
  simp only [← iteratedFderivWithin_univ]
  apply
    g.iterated_fderiv_within_comp_right hf.cont_diff_on uniqueDiffOn_univ uniqueDiffOn_univ
      (mem_univ _) hi
#align continuous_linear_map.iterated_fderiv_comp_right ContinuousLinearMap.iteratedFderiv_comp_right

/-- Composition with a linear isometry on the right preserves the norm of the iterated derivative
within a set. -/
theorem LinearIsometryEquiv.norm_iteratedFderivWithin_comp_right (g : G ≃ₗᵢ[𝕜] E) (f : E → F)
    (hs : UniqueDiffOn 𝕜 s) {x : G} (hx : g x ∈ s) (i : ℕ) :
    ‖iteratedFderivWithin 𝕜 i (f ∘ g) (g ⁻¹' s) x‖ = ‖iteratedFderivWithin 𝕜 i f s (g x)‖ :=
  by
  have :
    iteratedFderivWithin 𝕜 i (f ∘ g) (g ⁻¹' s) x =
      (iteratedFderivWithin 𝕜 i f s (g x)).compContinuousLinearMap fun _ => g :=
    g.to_continuous_linear_equiv.iterated_fderiv_within_comp_right f hs hx i
  rw [this, ContinuousMultilinearMap.norm_comp_continuous_linearIsometryEquiv]
#align linear_isometry_equiv.norm_iterated_fderiv_within_comp_right LinearIsometryEquiv.norm_iteratedFderivWithin_comp_right

/-- Composition with a linear isometry on the right preserves the norm of the iterated derivative
within a set. -/
theorem LinearIsometryEquiv.norm_iteratedFderiv_comp_right (g : G ≃ₗᵢ[𝕜] E) (f : E → F) (x : G)
    (i : ℕ) : ‖iteratedFderiv 𝕜 i (f ∘ g) x‖ = ‖iteratedFderiv 𝕜 i f (g x)‖ :=
  by
  simp only [← iteratedFderivWithin_univ]
  apply g.norm_iterated_fderiv_within_comp_right f uniqueDiffOn_univ (mem_univ (g x)) i
#align linear_isometry_equiv.norm_iterated_fderiv_comp_right LinearIsometryEquiv.norm_iteratedFderiv_comp_right

/-- Composition by continuous linear equivs on the right respects higher differentiability at a
point in a domain. -/
theorem ContinuousLinearEquiv.contDiffWithinAt_comp_iff (e : G ≃L[𝕜] E) :
    ContDiffWithinAt 𝕜 n (f ∘ e) (e ⁻¹' s) (e.symm x) ↔ ContDiffWithinAt 𝕜 n f s x :=
  by
  constructor
  · intro H
    simpa [← preimage_comp, (· ∘ ·)] using H.comp_continuous_linear_map (e.symm : E →L[𝕜] G)
  · intro H
    rw [← e.apply_symm_apply x, ← e.coe_coe] at H
    exact H.comp_continuous_linear_map _
#align continuous_linear_equiv.cont_diff_within_at_comp_iff ContinuousLinearEquiv.contDiffWithinAt_comp_iff

/-- Composition by continuous linear equivs on the right respects higher differentiability at a
point. -/
theorem ContinuousLinearEquiv.contDiffAt_comp_iff (e : G ≃L[𝕜] E) :
    ContDiffAt 𝕜 n (f ∘ e) (e.symm x) ↔ ContDiffAt 𝕜 n f x :=
  by
  rw [← contDiffWithinAt_univ, ← contDiffWithinAt_univ, ← preimage_univ]
  exact e.cont_diff_within_at_comp_iff
#align continuous_linear_equiv.cont_diff_at_comp_iff ContinuousLinearEquiv.contDiffAt_comp_iff

/-- Composition by continuous linear equivs on the right respects higher differentiability on
domains. -/
theorem ContinuousLinearEquiv.contDiffOn_comp_iff (e : G ≃L[𝕜] E) :
    ContDiffOn 𝕜 n (f ∘ e) (e ⁻¹' s) ↔ ContDiffOn 𝕜 n f s :=
  by
  refine' ⟨fun H => _, fun H => H.compContinuousLinearMap (e : G →L[𝕜] E)⟩
  have A : f = (f ∘ e) ∘ e.symm := by
    ext y
    simp only [Function.comp_apply]
    rw [e.apply_symm_apply y]
  have B : e.symm ⁻¹' (e ⁻¹' s) = s :=
    by
    rw [← preimage_comp, e.self_comp_symm]
    rfl
  rw [A, ← B]
  exact H.comp_continuous_linear_map (e.symm : E →L[𝕜] G)
#align continuous_linear_equiv.cont_diff_on_comp_iff ContinuousLinearEquiv.contDiffOn_comp_iff

/-- Composition by continuous linear equivs on the right respects higher differentiability. -/
theorem ContinuousLinearEquiv.contDiff_comp_iff (e : G ≃L[𝕜] E) :
    ContDiff 𝕜 n (f ∘ e) ↔ ContDiff 𝕜 n f :=
  by
  rw [← contDiffOn_univ, ← contDiffOn_univ, ← preimage_univ]
  exact e.cont_diff_on_comp_iff
#align continuous_linear_equiv.cont_diff_comp_iff ContinuousLinearEquiv.contDiff_comp_iff

/-- If two functions `f` and `g` admit Taylor series `p` and `q` in a set `s`, then the cartesian
product of `f` and `g` admits the cartesian product of `p` and `q` as a Taylor series. -/
theorem HasFtaylorSeriesUpToOn.prod (hf : HasFtaylorSeriesUpToOn n f p s) {g : E → G}
    {q : E → FormalMultilinearSeries 𝕜 E G} (hg : HasFtaylorSeriesUpToOn n g q s) :
    HasFtaylorSeriesUpToOn n (fun y => (f y, g y)) (fun y k => (p y k).Prod (q y k)) s :=
  by
  set L := fun m => ContinuousMultilinearMap.prodL 𝕜 (fun i : Fin m => E) F G
  constructor
  · intro x hx
    rw [← hf.zero_eq x hx, ← hg.zero_eq x hx]
    rfl
  · intro m hm x hx
    convert(L m).HasFderivAt.comp_hasFderivWithinAt x
        ((hf.fderiv_within m hm x hx).Prod (hg.fderiv_within m hm x hx))
  · intro m hm
    exact (L m).Continuous.comp_continuousOn ((hf.cont m hm).Prod (hg.cont m hm))
#align has_ftaylor_series_up_to_on.prod HasFtaylorSeriesUpToOn.prod

/-- The cartesian product of `C^n` functions at a point in a domain is `C^n`. -/
theorem ContDiffWithinAt.prod {s : Set E} {f : E → F} {g : E → G} (hf : ContDiffWithinAt 𝕜 n f s x)
    (hg : ContDiffWithinAt 𝕜 n g s x) : ContDiffWithinAt 𝕜 n (fun x : E => (f x, g x)) s x :=
  by
  intro m hm
  rcases hf m hm with ⟨u, hu, p, hp⟩
  rcases hg m hm with ⟨v, hv, q, hq⟩
  exact
    ⟨u ∩ v, Filter.inter_mem hu hv, _,
      (hp.mono (inter_subset_left u v)).Prod (hq.mono (inter_subset_right u v))⟩
#align cont_diff_within_at.prod ContDiffWithinAt.prod

/-- The cartesian product of `C^n` functions on domains is `C^n`. -/
theorem ContDiffOn.prod {s : Set E} {f : E → F} {g : E → G} (hf : ContDiffOn 𝕜 n f s)
    (hg : ContDiffOn 𝕜 n g s) : ContDiffOn 𝕜 n (fun x : E => (f x, g x)) s := fun x hx =>
  (hf x hx).Prod (hg x hx)
#align cont_diff_on.prod ContDiffOn.prod

/-- The cartesian product of `C^n` functions at a point is `C^n`. -/
theorem ContDiffAt.prod {f : E → F} {g : E → G} (hf : ContDiffAt 𝕜 n f x)
    (hg : ContDiffAt 𝕜 n g x) : ContDiffAt 𝕜 n (fun x : E => (f x, g x)) x :=
  contDiffWithinAt_univ.1 <|
    ContDiffWithinAt.prod (contDiffWithinAt_univ.2 hf) (contDiffWithinAt_univ.2 hg)
#align cont_diff_at.prod ContDiffAt.prod

/-- The cartesian product of `C^n` functions is `C^n`.-/
theorem ContDiff.prod {f : E → F} {g : E → G} (hf : ContDiff 𝕜 n f) (hg : ContDiff 𝕜 n g) :
    ContDiff 𝕜 n fun x : E => (f x, g x) :=
  contDiffOn_univ.1 <| ContDiffOn.prod (contDiffOn_univ.2 hf) (contDiffOn_univ.2 hg)
#align cont_diff.prod ContDiff.prod

/-!
### Composition of `C^n` functions

We show that the composition of `C^n` functions is `C^n`. One way to prove it would be to write
the `n`-th derivative of the composition (this is Faà di Bruno's formula) and check its continuity,
but this is very painful. Instead, we go for a simple inductive proof. Assume it is done for `n`.
Then, to check it for `n+1`, one needs to check that the derivative of `g ∘ f` is `C^n`, i.e.,
that `Dg(f x) ⬝ Df(x)` is `C^n`. The term `Dg (f x)` is the composition of two `C^n` functions, so
it is `C^n` by the inductive assumption. The term `Df(x)` is also `C^n`. Then, the matrix
multiplication is the application of a bilinear map (which is `C^∞`, and therefore `C^n`) to
`x ↦ (Dg(f x), Df x)`. As the composition of two `C^n` maps, it is again `C^n`, and we are done.

There is a subtlety in this argument: we apply the inductive assumption to functions on other Banach
spaces. In maths, one would say: prove by induction over `n` that, for all `C^n` maps between all
pairs of Banach spaces, their composition is `C^n`. In Lean, this is fine as long as the spaces
stay in the same universe. This is not the case in the above argument: if `E` lives in universe `u`
and `F` lives in universe `v`, then linear maps from `E` to `F` (to which the derivative of `f`
belongs) is in universe `max u v`. If one could quantify over finitely many universes, the above
proof would work fine, but this is not the case. One could still write the proof considering spaces
in any universe in `u, v, w, max u v, max v w, max u v w`, but it would be extremely tedious and
lead to a lot of duplication. Instead, we formulate the above proof when all spaces live in the same
universe (where everything is fine), and then we deduce the general result by lifting all our spaces
to a common universe. We use the trick that any space `H` is isomorphic through a continuous linear
equiv to `continuous_multilinear_map (λ (i : fin 0), E × F × G) H` to change the universe level,
and then argue that composing with such a linear equiv does not change the fact of being `C^n`,
which we have already proved previously.
-/

/-- Auxiliary lemma proving that the composition of `C^n` functions on domains is `C^n` when all
spaces live in the same universe. Use instead `cont_diff_on.comp` which removes the universe
assumption (but is deduced from this one). -/
private theorem cont_diff_on.comp_same_univ {Eu : Type u} [NormedAddCommGroup Eu] [NormedSpace 𝕜 Eu]
    {Fu : Type u} [NormedAddCommGroup Fu] [NormedSpace 𝕜 Fu] {Gu : Type u} [NormedAddCommGroup Gu]
    [NormedSpace 𝕜 Gu] {s : Set Eu} {t : Set Fu} {g : Fu → Gu} {f : Eu → Fu}
    (hg : ContDiffOn 𝕜 n g t) (hf : ContDiffOn 𝕜 n f s) (st : s ⊆ f ⁻¹' t) :
    ContDiffOn 𝕜 n (g ∘ f) s :=
  by
  induction' n using ENat.nat_induction with n IH Itop generalizing Eu Fu Gu
  · rw [contDiffOn_zero] at hf hg⊢
    exact ContinuousOn.comp hg hf st
  · rw [contDiffOn_succ_iff_hasFderivWithinAt] at hg⊢
    intro x hx
    rcases(contDiffOn_succ_iff_hasFderivWithinAt.1 hf) x hx with ⟨u, hu, f', hf', f'_diff⟩
    rcases hg (f x) (st hx) with ⟨v, hv, g', hg', g'_diff⟩
    rw [insert_eq_of_mem hx] at hu⊢
    have xu : x ∈ u := mem_of_mem_nhdsWithin hx hu
    let w := s ∩ (u ∩ f ⁻¹' v)
    have wv : w ⊆ f ⁻¹' v := fun y hy => hy.2.2
    have wu : w ⊆ u := fun y hy => hy.2.1
    have ws : w ⊆ s := fun y hy => hy.1
    refine' ⟨w, _, fun y => (g' (f y)).comp (f' y), _, _⟩
    show w ∈ 𝓝[s] x
    · apply Filter.inter_mem self_mem_nhdsWithin
      apply Filter.inter_mem hu
      apply ContinuousWithinAt.preimage_mem_nhds_within'
      · rw [← continuousWithinAt_inter' hu]
        exact (hf' x xu).DifferentiableWithinAt.ContinuousWithinAt.mono (inter_subset_right _ _)
      · apply nhdsWithin_mono _ _ hv
        exact subset.trans (image_subset_iff.mpr st) (subset_insert (f x) t)
    show ∀ y ∈ w, HasFderivWithinAt (g ∘ f) ((g' (f y)).comp (f' y)) w y
    · rintro y ⟨ys, yu, yv⟩
      exact (hg' (f y) yv).comp y ((hf' y yu).mono wu) wv
    show ContDiffOn 𝕜 n (fun y => (g' (f y)).comp (f' y)) w
    · have A : ContDiffOn 𝕜 n (fun y => g' (f y)) w :=
        IH g'_diff ((hf.of_le (WithTop.coe_le_coe.2 (Nat.le_succ n))).mono ws) wv
      have B : ContDiffOn 𝕜 n f' w := f'_diff.mono wu
      have C : ContDiffOn 𝕜 n (fun y => (g' (f y), f' y)) w := A.prod B
      have D : ContDiffOn 𝕜 n (fun p : (Fu →L[𝕜] Gu) × (Eu →L[𝕜] Fu) => p.1.comp p.2) univ :=
        is_bounded_bilinear_map_comp.cont_diff.cont_diff_on
      exact IH D C (subset_univ _)
  · rw [contDiffOn_top] at hf hg⊢
    exact fun n => Itop n (hg n) (hf n) st
#align cont_diff_on.comp_same_univ cont_diff_on.comp_same_univ

/-- The composition of `C^n` functions on domains is `C^n`. -/
theorem ContDiffOn.comp {s : Set E} {t : Set F} {g : F → G} {f : E → F} (hg : ContDiffOn 𝕜 n g t)
    (hf : ContDiffOn 𝕜 n f s) (st : s ⊆ f ⁻¹' t) : ContDiffOn 𝕜 n (g ∘ f) s :=
  by
  /- we lift all the spaces to a common universe, as we have already proved the result in this
    situation. -/
  let Eu : Type max uE uF uG := ULift E
  let Fu : Type max uE uF uG := ULift.{max uE uG, uF} F
  let Gu : Type max uE uF uG := ULift.{max uE uF, uG} G
  -- declare the isomorphisms
  have isoE : Eu ≃L[𝕜] E := ContinuousLinearEquiv.ulift
  have isoF : Fu ≃L[𝕜] F := ContinuousLinearEquiv.ulift
  have isoG : Gu ≃L[𝕜] G := ContinuousLinearEquiv.ulift
  -- lift the functions to the new spaces, check smoothness there, and then go back.
  let fu : Eu → Fu := (isoF.symm ∘ f) ∘ isoE
  have fu_diff : ContDiffOn 𝕜 n fu (isoE ⁻¹' s) := by
    rwa [isoE.cont_diff_on_comp_iff, isoF.symm.comp_cont_diff_on_iff]
  let gu : Fu → Gu := (isoG.symm ∘ g) ∘ isoF
  have gu_diff : ContDiffOn 𝕜 n gu (isoF ⁻¹' t) := by
    rwa [isoF.cont_diff_on_comp_iff, isoG.symm.comp_cont_diff_on_iff]
  have main : ContDiffOn 𝕜 n (gu ∘ fu) (isoE ⁻¹' s) :=
    by
    apply cont_diff_on.comp_same_univ gu_diff fu_diff
    intro y hy
    simp only [fu, ContinuousLinearEquiv.coe_apply, Function.comp_apply, mem_preimage]
    rw [isoF.apply_symm_apply (f (isoE y))]
    exact st hy
  have : gu ∘ fu = (isoG.symm ∘ g ∘ f) ∘ isoE :=
    by
    ext y
    simp only [Function.comp_apply, gu, fu]
    rw [isoF.apply_symm_apply (f (isoE y))]
  rwa [this, isoE.cont_diff_on_comp_iff, isoG.symm.comp_cont_diff_on_iff] at main
#align cont_diff_on.comp ContDiffOn.comp

/-- The composition of `C^n` functions on domains is `C^n`. -/
theorem ContDiffOn.comp' {s : Set E} {t : Set F} {g : F → G} {f : E → F} (hg : ContDiffOn 𝕜 n g t)
    (hf : ContDiffOn 𝕜 n f s) : ContDiffOn 𝕜 n (g ∘ f) (s ∩ f ⁻¹' t) :=
  hg.comp (hf.mono (inter_subset_left _ _)) (inter_subset_right _ _)
#align cont_diff_on.comp' ContDiffOn.comp'

/-- The composition of a `C^n` function on a domain with a `C^n` function is `C^n`. -/
theorem ContDiff.comp_contDiffOn {s : Set E} {g : F → G} {f : E → F} (hg : ContDiff 𝕜 n g)
    (hf : ContDiffOn 𝕜 n f s) : ContDiffOn 𝕜 n (g ∘ f) s :=
  (contDiffOn_univ.2 hg).comp hf subset_preimage_univ
#align cont_diff.comp_cont_diff_on ContDiff.comp_contDiffOn

/-- The composition of `C^n` functions is `C^n`. -/
theorem ContDiff.comp {g : F → G} {f : E → F} (hg : ContDiff 𝕜 n g) (hf : ContDiff 𝕜 n f) :
    ContDiff 𝕜 n (g ∘ f) :=
  contDiffOn_univ.1 <| ContDiffOn.comp (contDiffOn_univ.2 hg) (contDiffOn_univ.2 hf) (subset_univ _)
#align cont_diff.comp ContDiff.comp

/-- The composition of `C^n` functions at points in domains is `C^n`. -/
theorem ContDiffWithinAt.comp {s : Set E} {t : Set F} {g : F → G} {f : E → F} (x : E)
    (hg : ContDiffWithinAt 𝕜 n g t (f x)) (hf : ContDiffWithinAt 𝕜 n f s x) (st : s ⊆ f ⁻¹' t) :
    ContDiffWithinAt 𝕜 n (g ∘ f) s x := by
  intro m hm
  rcases hg.cont_diff_on hm with ⟨u, u_nhd, ut, hu⟩
  rcases hf.cont_diff_on hm with ⟨v, v_nhd, vs, hv⟩
  have xmem : x ∈ f ⁻¹' u ∩ v :=
    ⟨(mem_of_mem_nhdsWithin (mem_insert (f x) _) u_nhd : _),
      mem_of_mem_nhdsWithin (mem_insert x s) v_nhd⟩
  have : f ⁻¹' u ∈ 𝓝[insert x s] x :=
    by
    apply hf.continuous_within_at.insert_self.preimage_mem_nhds_within'
    apply nhdsWithin_mono _ _ u_nhd
    rw [image_insert_eq]
    exact insert_subset_insert (image_subset_iff.mpr st)
  have Z :=
    (hu.comp (hv.mono (inter_subset_right (f ⁻¹' u) v)) (inter_subset_left _ _)).ContDiffWithinAt
      xmem m le_rfl
  have : 𝓝[f ⁻¹' u ∩ v] x = 𝓝[insert x s] x :=
    by
    have A : f ⁻¹' u ∩ v = insert x s ∩ (f ⁻¹' u ∩ v) :=
      by
      apply subset.antisymm _ (inter_subset_right _ _)
      rintro y ⟨hy1, hy2⟩
      simp [hy1, hy2, vs hy2]
    rw [A, ← nhdsWithin_restrict'']
    exact Filter.inter_mem this v_nhd
  rwa [insert_eq_of_mem xmem, this] at Z
#align cont_diff_within_at.comp ContDiffWithinAt.comp

/-- The composition of `C^n` functions at points in domains is `C^n`,
  with a weaker condition on `s` and `t`. -/
theorem ContDiffWithinAt.comp_of_mem {s : Set E} {t : Set F} {g : F → G} {f : E → F} (x : E)
    (hg : ContDiffWithinAt 𝕜 n g t (f x)) (hf : ContDiffWithinAt 𝕜 n f s x)
    (hs : t ∈ 𝓝[f '' s] f x) : ContDiffWithinAt 𝕜 n (g ∘ f) s x :=
  (hg.mono_of_mem hs).comp x hf (subset_preimage_image f s)
#align cont_diff_within_at.comp_of_mem ContDiffWithinAt.comp_of_mem

/-- The composition of `C^n` functions at points in domains is `C^n`. -/
theorem ContDiffWithinAt.comp' {s : Set E} {t : Set F} {g : F → G} {f : E → F} (x : E)
    (hg : ContDiffWithinAt 𝕜 n g t (f x)) (hf : ContDiffWithinAt 𝕜 n f s x) :
    ContDiffWithinAt 𝕜 n (g ∘ f) (s ∩ f ⁻¹' t) x :=
  hg.comp x (hf.mono (inter_subset_left _ _)) (inter_subset_right _ _)
#align cont_diff_within_at.comp' ContDiffWithinAt.comp'

theorem ContDiffAt.comp_contDiffWithinAt {n} (x : E) (hg : ContDiffAt 𝕜 n g (f x))
    (hf : ContDiffWithinAt 𝕜 n f s x) : ContDiffWithinAt 𝕜 n (g ∘ f) s x :=
  hg.comp x hf (mapsTo_univ _ _)
#align cont_diff_at.comp_cont_diff_within_at ContDiffAt.comp_contDiffWithinAt

/-- The composition of `C^n` functions at points is `C^n`. -/
theorem ContDiffAt.comp (x : E) (hg : ContDiffAt 𝕜 n g (f x)) (hf : ContDiffAt 𝕜 n f x) :
    ContDiffAt 𝕜 n (g ∘ f) x :=
  hg.comp x hf subset_preimage_univ
#align cont_diff_at.comp ContDiffAt.comp

theorem ContDiff.comp_contDiffWithinAt {g : F → G} {f : E → F} (h : ContDiff 𝕜 n g)
    (hf : ContDiffWithinAt 𝕜 n f t x) : ContDiffWithinAt 𝕜 n (g ∘ f) t x :=
  haveI : ContDiffWithinAt 𝕜 n g univ (f x) := h.cont_diff_at.cont_diff_within_at
  this.comp x hf (subset_univ _)
#align cont_diff.comp_cont_diff_within_at ContDiff.comp_contDiffWithinAt

theorem ContDiff.comp_contDiffAt {g : F → G} {f : E → F} (x : E) (hg : ContDiff 𝕜 n g)
    (hf : ContDiffAt 𝕜 n f x) : ContDiffAt 𝕜 n (g ∘ f) x :=
  hg.comp_contDiffWithinAt hf
#align cont_diff.comp_cont_diff_at ContDiff.comp_contDiffAt

/-!
### Smoothness of projections
-/

/-- The first projection in a product is `C^∞`. -/
theorem contDiff_fst : ContDiff 𝕜 n (Prod.fst : E × F → E) :=
  IsBoundedLinearMap.contDiff IsBoundedLinearMap.fst
#align cont_diff_fst contDiff_fst

/-- Postcomposing `f` with `prod.fst` is `C^n` -/
theorem ContDiff.fst {f : E → F × G} (hf : ContDiff 𝕜 n f) : ContDiff 𝕜 n fun x => (f x).1 :=
  contDiff_fst.comp hf
#align cont_diff.fst ContDiff.fst

/-- Precomposing `f` with `prod.fst` is `C^n` -/
theorem ContDiff.fst' {f : E → G} (hf : ContDiff 𝕜 n f) : ContDiff 𝕜 n fun x : E × F => f x.1 :=
  hf.comp contDiff_fst
#align cont_diff.fst' ContDiff.fst'

/-- The first projection on a domain in a product is `C^∞`. -/
theorem contDiffOn_fst {s : Set (E × F)} : ContDiffOn 𝕜 n (Prod.fst : E × F → E) s :=
  ContDiff.contDiffOn contDiff_fst
#align cont_diff_on_fst contDiffOn_fst

theorem ContDiffOn.fst {f : E → F × G} {s : Set E} (hf : ContDiffOn 𝕜 n f s) :
    ContDiffOn 𝕜 n (fun x => (f x).1) s :=
  contDiff_fst.comp_contDiffOn hf
#align cont_diff_on.fst ContDiffOn.fst

/-- The first projection at a point in a product is `C^∞`. -/
theorem contDiffAt_fst {p : E × F} : ContDiffAt 𝕜 n (Prod.fst : E × F → E) p :=
  contDiff_fst.ContDiffAt
#align cont_diff_at_fst contDiffAt_fst

/-- Postcomposing `f` with `prod.fst` is `C^n` at `(x, y)` -/
theorem ContDiffAt.fst {f : E → F × G} {x : E} (hf : ContDiffAt 𝕜 n f x) :
    ContDiffAt 𝕜 n (fun x => (f x).1) x :=
  contDiffAt_fst.comp x hf
#align cont_diff_at.fst ContDiffAt.fst

/-- Precomposing `f` with `prod.fst` is `C^n` at `(x, y)` -/
theorem ContDiffAt.fst' {f : E → G} {x : E} {y : F} (hf : ContDiffAt 𝕜 n f x) :
    ContDiffAt 𝕜 n (fun x : E × F => f x.1) (x, y) :=
  ContDiffAt.comp (x, y) hf contDiffAt_fst
#align cont_diff_at.fst' ContDiffAt.fst'

/-- Precomposing `f` with `prod.fst` is `C^n` at `x : E × F` -/
theorem ContDiffAt.fst'' {f : E → G} {x : E × F} (hf : ContDiffAt 𝕜 n f x.1) :
    ContDiffAt 𝕜 n (fun x : E × F => f x.1) x :=
  hf.comp x contDiffAt_fst
#align cont_diff_at.fst'' ContDiffAt.fst''

/-- The first projection within a domain at a point in a product is `C^∞`. -/
theorem contDiffWithinAt_fst {s : Set (E × F)} {p : E × F} :
    ContDiffWithinAt 𝕜 n (Prod.fst : E × F → E) s p :=
  contDiff_fst.ContDiffWithinAt
#align cont_diff_within_at_fst contDiffWithinAt_fst

/-- The second projection in a product is `C^∞`. -/
theorem contDiff_snd : ContDiff 𝕜 n (Prod.snd : E × F → F) :=
  IsBoundedLinearMap.contDiff IsBoundedLinearMap.snd
#align cont_diff_snd contDiff_snd

/-- Postcomposing `f` with `prod.snd` is `C^n` -/
theorem ContDiff.snd {f : E → F × G} (hf : ContDiff 𝕜 n f) : ContDiff 𝕜 n fun x => (f x).2 :=
  contDiff_snd.comp hf
#align cont_diff.snd ContDiff.snd

/-- Precomposing `f` with `prod.snd` is `C^n` -/
theorem ContDiff.snd' {f : F → G} (hf : ContDiff 𝕜 n f) : ContDiff 𝕜 n fun x : E × F => f x.2 :=
  hf.comp contDiff_snd
#align cont_diff.snd' ContDiff.snd'

/-- The second projection on a domain in a product is `C^∞`. -/
theorem contDiffOn_snd {s : Set (E × F)} : ContDiffOn 𝕜 n (Prod.snd : E × F → F) s :=
  ContDiff.contDiffOn contDiff_snd
#align cont_diff_on_snd contDiffOn_snd

theorem ContDiffOn.snd {f : E → F × G} {s : Set E} (hf : ContDiffOn 𝕜 n f s) :
    ContDiffOn 𝕜 n (fun x => (f x).2) s :=
  contDiff_snd.comp_contDiffOn hf
#align cont_diff_on.snd ContDiffOn.snd

/-- The second projection at a point in a product is `C^∞`. -/
theorem contDiffAt_snd {p : E × F} : ContDiffAt 𝕜 n (Prod.snd : E × F → F) p :=
  contDiff_snd.ContDiffAt
#align cont_diff_at_snd contDiffAt_snd

/-- Postcomposing `f` with `prod.snd` is `C^n` at `x` -/
theorem ContDiffAt.snd {f : E → F × G} {x : E} (hf : ContDiffAt 𝕜 n f x) :
    ContDiffAt 𝕜 n (fun x => (f x).2) x :=
  contDiffAt_snd.comp x hf
#align cont_diff_at.snd ContDiffAt.snd

/-- Precomposing `f` with `prod.snd` is `C^n` at `(x, y)` -/
theorem ContDiffAt.snd' {f : F → G} {x : E} {y : F} (hf : ContDiffAt 𝕜 n f y) :
    ContDiffAt 𝕜 n (fun x : E × F => f x.2) (x, y) :=
  ContDiffAt.comp (x, y) hf contDiffAt_snd
#align cont_diff_at.snd' ContDiffAt.snd'

/-- Precomposing `f` with `prod.snd` is `C^n` at `x : E × F` -/
theorem ContDiffAt.snd'' {f : F → G} {x : E × F} (hf : ContDiffAt 𝕜 n f x.2) :
    ContDiffAt 𝕜 n (fun x : E × F => f x.2) x :=
  hf.comp x contDiffAt_snd
#align cont_diff_at.snd'' ContDiffAt.snd''

/-- The second projection within a domain at a point in a product is `C^∞`. -/
theorem contDiffWithinAt_snd {s : Set (E × F)} {p : E × F} :
    ContDiffWithinAt 𝕜 n (Prod.snd : E × F → F) s p :=
  contDiff_snd.ContDiffWithinAt
#align cont_diff_within_at_snd contDiffWithinAt_snd

section NAry

variable {E₁ E₂ E₃ E₄ : Type _}

variable [NormedAddCommGroup E₁] [NormedAddCommGroup E₂] [NormedAddCommGroup E₃]
  [NormedAddCommGroup E₄] [NormedSpace 𝕜 E₁] [NormedSpace 𝕜 E₂] [NormedSpace 𝕜 E₃]
  [NormedSpace 𝕜 E₄]

theorem ContDiff.comp₂ {g : E₁ × E₂ → G} {f₁ : F → E₁} {f₂ : F → E₂} (hg : ContDiff 𝕜 n g)
    (hf₁ : ContDiff 𝕜 n f₁) (hf₂ : ContDiff 𝕜 n f₂) : ContDiff 𝕜 n fun x => g (f₁ x, f₂ x) :=
  hg.comp <| hf₁.Prod hf₂
#align cont_diff.comp₂ ContDiff.comp₂

theorem ContDiff.comp₃ {g : E₁ × E₂ × E₃ → G} {f₁ : F → E₁} {f₂ : F → E₂} {f₃ : F → E₃}
    (hg : ContDiff 𝕜 n g) (hf₁ : ContDiff 𝕜 n f₁) (hf₂ : ContDiff 𝕜 n f₂) (hf₃ : ContDiff 𝕜 n f₃) :
    ContDiff 𝕜 n fun x => g (f₁ x, f₂ x, f₃ x) :=
  hg.comp₂ hf₁ <| hf₂.Prod hf₃
#align cont_diff.comp₃ ContDiff.comp₃

theorem ContDiff.comp_contDiff_on₂ {g : E₁ × E₂ → G} {f₁ : F → E₁} {f₂ : F → E₂} {s : Set F}
    (hg : ContDiff 𝕜 n g) (hf₁ : ContDiffOn 𝕜 n f₁ s) (hf₂ : ContDiffOn 𝕜 n f₂ s) :
    ContDiffOn 𝕜 n (fun x => g (f₁ x, f₂ x)) s :=
  hg.comp_contDiffOn <| hf₁.Prod hf₂
#align cont_diff.comp_cont_diff_on₂ ContDiff.comp_contDiff_on₂

theorem ContDiff.comp_contDiff_on₃ {g : E₁ × E₂ × E₃ → G} {f₁ : F → E₁} {f₂ : F → E₂} {f₃ : F → E₃}
    {s : Set F} (hg : ContDiff 𝕜 n g) (hf₁ : ContDiffOn 𝕜 n f₁ s) (hf₂ : ContDiffOn 𝕜 n f₂ s)
    (hf₃ : ContDiffOn 𝕜 n f₃ s) : ContDiffOn 𝕜 n (fun x => g (f₁ x, f₂ x, f₃ x)) s :=
  hg.comp_contDiff_on₂ hf₁ <| hf₂.Prod hf₃
#align cont_diff.comp_cont_diff_on₃ ContDiff.comp_contDiff_on₃

end NAry

section SpecificBilinearMaps

theorem ContDiff.clm_comp {g : X → F →L[𝕜] G} {f : X → E →L[𝕜] F} (hg : ContDiff 𝕜 n g)
    (hf : ContDiff 𝕜 n f) : ContDiff 𝕜 n fun x => (g x).comp (f x) :=
  isBoundedBilinearMapComp.ContDiff.comp₂ hg hf
#align cont_diff.clm_comp ContDiff.clm_comp

theorem ContDiffOn.clm_comp {g : X → F →L[𝕜] G} {f : X → E →L[𝕜] F} {s : Set X}
    (hg : ContDiffOn 𝕜 n g s) (hf : ContDiffOn 𝕜 n f s) :
    ContDiffOn 𝕜 n (fun x => (g x).comp (f x)) s :=
  isBoundedBilinearMapComp.ContDiff.comp_contDiff_on₂ hg hf
#align cont_diff_on.clm_comp ContDiffOn.clm_comp

theorem ContDiff.clm_apply {f : E → F →L[𝕜] G} {g : E → F} {n : ℕ∞} (hf : ContDiff 𝕜 n f)
    (hg : ContDiff 𝕜 n g) : ContDiff 𝕜 n fun x => (f x) (g x) :=
  isBoundedBilinearMapApply.ContDiff.comp₂ hf hg
#align cont_diff.clm_apply ContDiff.clm_apply

theorem ContDiffOn.clm_apply {f : E → F →L[𝕜] G} {g : E → F} {n : ℕ∞} (hf : ContDiffOn 𝕜 n f s)
    (hg : ContDiffOn 𝕜 n g s) : ContDiffOn 𝕜 n (fun x => (f x) (g x)) s :=
  isBoundedBilinearMapApply.ContDiff.comp_contDiff_on₂ hf hg
#align cont_diff_on.clm_apply ContDiffOn.clm_apply

theorem ContDiff.smulRight {f : E → F →L[𝕜] 𝕜} {g : E → G} {n : ℕ∞} (hf : ContDiff 𝕜 n f)
    (hg : ContDiff 𝕜 n g) : ContDiff 𝕜 n fun x => (f x).smul_right (g x) :=
  (-- giving the following implicit type arguments speeds up elaboration significantly
          @isBoundedBilinearMapSmulRight
          𝕜 _ F _ _ G _ _).ContDiff.comp₂
    hf hg
#align cont_diff.smul_right ContDiff.smulRight

end SpecificBilinearMaps

/-- The natural equivalence `(E × F) × G ≃ E × (F × G)` is smooth.

Warning: if you think you need this lemma, it is likely that you can simplify your proof by
reformulating the lemma that you're applying next using the tips in
Note [continuity lemma statement]
-/
theorem contDiff_prodAssoc : ContDiff 𝕜 ⊤ <| Equiv.prodAssoc E F G :=
  (LinearIsometryEquiv.prodAssoc 𝕜 E F G).ContDiff
#align cont_diff_prod_assoc contDiff_prodAssoc

/-- The natural equivalence `E × (F × G) ≃ (E × F) × G` is smooth.

Warning: see remarks attached to `cont_diff_prod_assoc`
-/
theorem contDiff_prodAssoc_symm : ContDiff 𝕜 ⊤ <| (Equiv.prodAssoc E F G).symm :=
  (LinearIsometryEquiv.prodAssoc 𝕜 E F G).symm.ContDiff
#align cont_diff_prod_assoc_symm contDiff_prodAssoc_symm

/-! ### Bundled derivatives are smooth -/

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- One direction of `cont_diff_within_at_succ_iff_has_fderiv_within_at`, but where all derivatives
	are taken within the same set. Version for partial derivatives / functions with parameters.
	If `f x` is a `C^n+1` family of functions and `g x` is a `C^n` family of points, then the
  derivative of `f x` at `g x` depends in a `C^n` way on `x`. We give a general version of this fact
  relative to sets which may not have unique derivatives, in the following form.
	If `f : E × F → G` is `C^n+1` at `(x₀, g(x₀))` in `(s ∪ {x₀}) × t ⊆ E × F` and `g : E → F` is
	`C^n` at `x₀` within some set `s ⊆ E`, then there is a function `f' : E → F →L[𝕜] G`
	that is `C^n` at `x₀` within `s` such that for all `x` sufficiently close to `x₀` within
	`s ∪ {x₀}` the function `y ↦ f x y` has derivative `f' x` at `g x` within `t ⊆ F`.
	For convenience, we return an explicit set of `x`'s where this holds that is a subset of
	`s ∪ {x₀}`.
	We need one additional condition, namely that `t` is a neighborhood of `g(x₀)` within `g '' s`.
	-/
theorem ContDiffWithinAt.hasFderivWithinAt_nhds {f : E → F → G} {g : E → F} {t : Set F} {n : ℕ}
    {x₀ : E} (hf : ContDiffWithinAt 𝕜 (n + 1) (uncurry f) (insert x₀ s ×ˢ t) (x₀, g x₀))
    (hg : ContDiffWithinAt 𝕜 n g s x₀) (hgt : t ∈ 𝓝[g '' s] g x₀) :
    ∃ v ∈ 𝓝[insert x₀ s] x₀,
      v ⊆ insert x₀ s ∧
        ∃ f' : E → F →L[𝕜] G,
          (∀ x ∈ v, HasFderivWithinAt (f x) (f' x) t (g x)) ∧
            ContDiffWithinAt 𝕜 n (fun x => f' x) s x₀ :=
  by
  have hst : insert x₀ s ×ˢ t ∈ 𝓝[(fun x => (x, g x)) '' s] (x₀, g x₀) :=
    by
    refine' nhdsWithin_mono _ _ (nhdsWithin_prod self_mem_nhdsWithin hgt)
    simp_rw [image_subset_iff, mk_preimage_prod, preimage_id', subset_inter_iff, subset_insert,
      true_and_iff, subset_preimage_image]
  obtain ⟨v, hv, hvs, f', hvf', hf'⟩ := cont_diff_within_at_succ_iff_has_fderiv_within_at'.mp hf
  refine'
    ⟨(fun z => (z, g z)) ⁻¹' v ∩ insert x₀ s, _, inter_subset_right _ _, fun z =>
      (f' (z, g z)).comp (ContinuousLinearMap.inr 𝕜 E F), _, _⟩
  · refine' inter_mem _ self_mem_nhdsWithin
    have := mem_of_mem_nhdsWithin (mem_insert _ _) hv
    refine' mem_nhds_within_insert.mpr ⟨this, _⟩
    refine' (continuous_within_at_id.prod hg.continuous_within_at).preimage_mem_nhds_within' _
    rw [← nhdsWithin_le_iff] at hst hv⊢
    refine' (hst.trans <| nhdsWithin_mono _ <| subset_insert _ _).trans hv
  · intro z hz
    have := hvf' (z, g z) hz.1
    refine' this.comp _ (hasFderivAt_prod_mk_right _ _).HasFderivWithinAt _
    exact maps_to'.mpr (image_prod_mk_subset_prod_right hz.2)
  ·
    exact
      (hf'.continuous_linear_map_comp <|
            (ContinuousLinearMap.compL 𝕜 F (E × F) G).flip
              (ContinuousLinearMap.inr 𝕜 E F)).comp_of_mem
        x₀ (cont_diff_within_at_id.prod hg) hst
#align cont_diff_within_at.has_fderiv_within_at_nhds ContDiffWithinAt.hasFderivWithinAt_nhds

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- The most general lemma stating that `x ↦ fderiv_within 𝕜 (f x) t (g x)` is `C^n`
at a point within a set.
To show that `x ↦ D_yf(x,y)g(x)` (taken within `t`) is `C^m` at `x₀` within `s`, we require that
* `f` is `C^n` at `(x₀, g(x₀))` within `(s ∪ {x₀}) × t` for `n ≥ m+1`.
* `g` is `C^m` at `x₀` within `s`;
* Derivatives are unique at `g(x)` within `t` for `x` sufficiently close to `x₀` within `s ∪ {x₀}`;
* `t` is a neighborhood of `g(x₀)` within `g '' s`; -/
theorem ContDiffWithinAt.fderiv_within'' {f : E → F → G} {g : E → F} {t : Set F} {n : ℕ∞}
    (hf : ContDiffWithinAt 𝕜 n (Function.uncurry f) (insert x₀ s ×ˢ t) (x₀, g x₀))
    (hg : ContDiffWithinAt 𝕜 m g s x₀)
    (ht : ∀ᶠ x in 𝓝[insert x₀ s] x₀, UniqueDiffWithinAt 𝕜 t (g x)) (hmn : m + 1 ≤ n)
    (hgt : t ∈ 𝓝[g '' s] g x₀) :
    ContDiffWithinAt 𝕜 m (fun x => fderivWithin 𝕜 (f x) t (g x)) s x₀ :=
  by
  have :
    ∀ k : ℕ, (k : ℕ∞) ≤ m → ContDiffWithinAt 𝕜 k (fun x => fderivWithin 𝕜 (f x) t (g x)) s x₀ :=
    by
    intro k hkm
    obtain ⟨v, hv, -, f', hvf', hf'⟩ :=
      (hf.of_le <| (add_le_add_right hkm 1).trans hmn).hasFderivWithinAt_nhds (hg.of_le hkm) hgt
    refine' hf'.congr_of_eventually_eq_insert _
    filter_upwards [hv, ht]
    exact fun y hy h2y => (hvf' y hy).fderivWithin h2y
  induction m using WithTop.recTopCoe
  · obtain rfl := eq_top_iff.mpr hmn
    rw [contDiffWithinAt_top]
    exact fun m => this m le_top
  exact this m le_rfl
#align cont_diff_within_at.fderiv_within'' ContDiffWithinAt.fderiv_within''

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- A special case of `cont_diff_within_at.fderiv_within''` where we require that `s ⊆ g⁻¹(t)`. -/
theorem ContDiffWithinAt.fderiv_within' {f : E → F → G} {g : E → F} {t : Set F} {n : ℕ∞}
    (hf : ContDiffWithinAt 𝕜 n (Function.uncurry f) (insert x₀ s ×ˢ t) (x₀, g x₀))
    (hg : ContDiffWithinAt 𝕜 m g s x₀)
    (ht : ∀ᶠ x in 𝓝[insert x₀ s] x₀, UniqueDiffWithinAt 𝕜 t (g x)) (hmn : m + 1 ≤ n)
    (hst : s ⊆ g ⁻¹' t) : ContDiffWithinAt 𝕜 m (fun x => fderivWithin 𝕜 (f x) t (g x)) s x₀ :=
  hf.fderiv_within'' hg ht hmn <| mem_of_superset self_mem_nhdsWithin <| image_subset_iff.mpr hst
#align cont_diff_within_at.fderiv_within' ContDiffWithinAt.fderiv_within'

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- A special case of `cont_diff_within_at.fderiv_within'` where we require that `x₀ ∈ s` and there
  are unique derivatives everywhere within `t`. -/
theorem ContDiffWithinAt.fderivWithin {f : E → F → G} {g : E → F} {t : Set F} {n : ℕ∞}
    (hf : ContDiffWithinAt 𝕜 n (Function.uncurry f) (s ×ˢ t) (x₀, g x₀))
    (hg : ContDiffWithinAt 𝕜 m g s x₀) (ht : UniqueDiffOn 𝕜 t) (hmn : m + 1 ≤ n) (hx₀ : x₀ ∈ s)
    (hst : s ⊆ g ⁻¹' t) : ContDiffWithinAt 𝕜 m (fun x => fderivWithin 𝕜 (f x) t (g x)) s x₀ :=
  by
  rw [← insert_eq_self.mpr hx₀] at hf
  refine' hf.fderiv_within' hg _ hmn hst
  rw [insert_eq_self.mpr hx₀]
  exact eventually_of_mem self_mem_nhdsWithin fun x hx => ht _ (hst hx)
#align cont_diff_within_at.fderiv_within ContDiffWithinAt.fderivWithin

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- `x ↦ fderiv_within 𝕜 (f x) t (g x) (k x)` is smooth at a point within a set. -/
theorem ContDiffWithinAt.fderivWithin_apply {f : E → F → G} {g k : E → F} {t : Set F} {n : ℕ∞}
    (hf : ContDiffWithinAt 𝕜 n (Function.uncurry f) (s ×ˢ t) (x₀, g x₀))
    (hg : ContDiffWithinAt 𝕜 m g s x₀) (hk : ContDiffWithinAt 𝕜 m k s x₀) (ht : UniqueDiffOn 𝕜 t)
    (hmn : m + 1 ≤ n) (hx₀ : x₀ ∈ s) (hst : s ⊆ g ⁻¹' t) :
    ContDiffWithinAt 𝕜 m (fun x => fderivWithin 𝕜 (f x) t (g x) (k x)) s x₀ :=
  (contDiff_fst.clm_apply contDiff_snd).ContDiffAt.comp_contDiffWithinAt x₀
    ((hf.fderivWithin hg ht hmn hx₀ hst).Prod hk)
#align cont_diff_within_at.fderiv_within_apply ContDiffWithinAt.fderivWithin_apply

/-- `fderiv_within 𝕜 f s` is smooth at `x₀` within `s`. -/
theorem ContDiffWithinAt.fderivWithin_right (hf : ContDiffWithinAt 𝕜 n f s x₀)
    (hs : UniqueDiffOn 𝕜 s) (hmn : (m + 1 : ℕ∞) ≤ n) (hx₀s : x₀ ∈ s) :
    ContDiffWithinAt 𝕜 m (fderivWithin 𝕜 f s) s x₀ :=
  ContDiffWithinAt.fderivWithin
    (ContDiffWithinAt.comp (x₀, x₀) hf contDiffWithinAt_snd <| prod_subset_preimage_snd s s)
    contDiffWithinAt_id hs hmn hx₀s (by rw [preimage_id'])
#align cont_diff_within_at.fderiv_within_right ContDiffWithinAt.fderivWithin_right

/-- `x ↦ fderiv 𝕜 (f x) (g x)` is smooth at `x₀`. -/
theorem ContDiffAt.fderiv {f : E → F → G} {g : E → F} {n : ℕ∞}
    (hf : ContDiffAt 𝕜 n (Function.uncurry f) (x₀, g x₀)) (hg : ContDiffAt 𝕜 m g x₀)
    (hmn : m + 1 ≤ n) : ContDiffAt 𝕜 m (fun x => fderiv 𝕜 (f x) (g x)) x₀ :=
  by
  simp_rw [← fderivWithin_univ]
  refine'
    (ContDiffWithinAt.fderivWithin hf.cont_diff_within_at hg.cont_diff_within_at uniqueDiffOn_univ
          hmn (mem_univ x₀) _).ContDiffAt
      univ_mem
  rw [preimage_univ]
#align cont_diff_at.fderiv ContDiffAt.fderiv

/-- `fderiv 𝕜 f` is smooth at `x₀`. -/
theorem ContDiffAt.fderiv_right (hf : ContDiffAt 𝕜 n f x₀) (hmn : (m + 1 : ℕ∞) ≤ n) :
    ContDiffAt 𝕜 m (fderiv 𝕜 f) x₀ :=
  ContDiffAt.fderiv (ContDiffAt.comp (x₀, x₀) hf contDiffAt_snd) contDiffAt_id hmn
#align cont_diff_at.fderiv_right ContDiffAt.fderiv_right

/-- `x ↦ fderiv 𝕜 (f x) (g x)` is smooth. -/
theorem ContDiff.fderiv {f : E → F → G} {g : E → F} {n m : ℕ∞}
    (hf : ContDiff 𝕜 m <| Function.uncurry f) (hg : ContDiff 𝕜 n g) (hnm : n + 1 ≤ m) :
    ContDiff 𝕜 n fun x => fderiv 𝕜 (f x) (g x) :=
  contDiff_iff_contDiffAt.mpr fun x => hf.ContDiffAt.fderiv hg.ContDiffAt hnm
#align cont_diff.fderiv ContDiff.fderiv

/-- `fderiv 𝕜 f` is smooth. -/
theorem ContDiff.fderiv_right (hf : ContDiff 𝕜 n f) (hmn : (m + 1 : ℕ∞) ≤ n) :
    ContDiff 𝕜 m (fderiv 𝕜 f) :=
  contDiff_iff_contDiffAt.mpr fun x => hf.ContDiffAt.fderiv_right hmn
#align cont_diff.fderiv_right ContDiff.fderiv_right

/-- `x ↦ fderiv 𝕜 (f x) (g x)` is continuous. -/
theorem Continuous.fderiv {f : E → F → G} {g : E → F} {n : ℕ∞}
    (hf : ContDiff 𝕜 n <| Function.uncurry f) (hg : Continuous g) (hn : 1 ≤ n) :
    Continuous fun x => fderiv 𝕜 (f x) (g x) :=
  (hf.fderiv (contDiff_zero.mpr hg) hn).Continuous
#align continuous.fderiv Continuous.fderiv

/-- `x ↦ fderiv 𝕜 (f x) (g x) (k x)` is smooth. -/
theorem ContDiff.fderiv_apply {f : E → F → G} {g k : E → F} {n m : ℕ∞}
    (hf : ContDiff 𝕜 m <| Function.uncurry f) (hg : ContDiff 𝕜 n g) (hk : ContDiff 𝕜 n k)
    (hnm : n + 1 ≤ m) : ContDiff 𝕜 n fun x => fderiv 𝕜 (f x) (g x) (k x) :=
  (hf.fderiv hg hnm).clm_apply hk
#align cont_diff.fderiv_apply ContDiff.fderiv_apply

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- The bundled derivative of a `C^{n+1}` function is `C^n`. -/
theorem contDiffOn_fderivWithin_apply {m n : ℕ∞} {s : Set E} {f : E → F} (hf : ContDiffOn 𝕜 n f s)
    (hs : UniqueDiffOn 𝕜 s) (hmn : m + 1 ≤ n) :
    ContDiffOn 𝕜 m (fun p : E × E => (fderivWithin 𝕜 f s p.1 : E →L[𝕜] F) p.2) (s ×ˢ univ) :=
  ((hf.fderivWithin hs hmn).comp contDiffOn_fst (prod_subset_preimage_fst _ _)).clm_apply
    contDiffOn_snd
#align cont_diff_on_fderiv_within_apply contDiffOn_fderivWithin_apply

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- If a function is at least `C^1`, its bundled derivative (mapping `(x, v)` to `Df(x) v`) is
continuous. -/
theorem ContDiffOn.continuousOn_fderivWithin_apply (hf : ContDiffOn 𝕜 n f s) (hs : UniqueDiffOn 𝕜 s)
    (hn : 1 ≤ n) :
    ContinuousOn (fun p : E × E => (fderivWithin 𝕜 f s p.1 : E → F) p.2) (s ×ˢ univ) :=
  (contDiffOn_fderivWithin_apply hf hs <| by rwa [zero_add]).ContinuousOn
#align cont_diff_on.continuous_on_fderiv_within_apply ContDiffOn.continuousOn_fderivWithin_apply

/-- The bundled derivative of a `C^{n+1}` function is `C^n`. -/
theorem ContDiff.contDiff_fderiv_apply {f : E → F} (hf : ContDiff 𝕜 n f) (hmn : m + 1 ≤ n) :
    ContDiff 𝕜 m fun p : E × E => (fderiv 𝕜 f p.1 : E →L[𝕜] F) p.2 :=
  by
  rw [← contDiffOn_univ] at hf⊢
  rw [← fderivWithin_univ, ← univ_prod_univ]
  exact contDiffOn_fderivWithin_apply hf uniqueDiffOn_univ hmn
#align cont_diff.cont_diff_fderiv_apply ContDiff.contDiff_fderiv_apply

/-!
### Smoothness of functions `f : E → Π i, F' i`
-/

section Pi

variable {ι ι' : Type _} [Fintype ι] [Fintype ι'] {F' : ι → Type _} [∀ i, NormedAddCommGroup (F' i)]
  [∀ i, NormedSpace 𝕜 (F' i)] {φ : ∀ i, E → F' i} {p' : ∀ i, E → FormalMultilinearSeries 𝕜 E (F' i)}
  {Φ : E → ∀ i, F' i} {P' : E → FormalMultilinearSeries 𝕜 E (∀ i, F' i)}

theorem hasFtaylorSeriesUpToOn_pi :
    HasFtaylorSeriesUpToOn n (fun x i => φ i x)
        (fun x m => ContinuousMultilinearMap.pi fun i => p' i x m) s ↔
      ∀ i, HasFtaylorSeriesUpToOn n (φ i) (p' i) s :=
  by
  set pr := @ContinuousLinearMap.proj 𝕜 _ ι F' _ _ _
  letI : ∀ (m : ℕ) (i : ι), NormedSpace 𝕜 (E[×m]→L[𝕜] F' i) := fun m i => inferInstance
  set L : ∀ m : ℕ, (∀ i, E[×m]→L[𝕜] F' i) ≃ₗᵢ[𝕜] E[×m]→L[𝕜] ∀ i, F' i := fun m =>
    ContinuousMultilinearMap.piₗᵢ _ _
  refine' ⟨fun h i => _, fun h => ⟨fun x hx => _, _, _⟩⟩
  · convert h.continuous_linear_map_comp (pr i)
    ext
    rfl
  · ext1 i
    exact (h i).zero_eq x hx
  · intro m hm x hx
    have := hasFderivWithinAt_pi.2 fun i => (h i).fderivWithin m hm x hx
    convert(L m).HasFderivAt.comp_hasFderivWithinAt x this
  · intro m hm
    have := continuousOn_pi.2 fun i => (h i).cont m hm
    convert(L m).Continuous.comp_continuousOn this
#align has_ftaylor_series_up_to_on_pi hasFtaylorSeriesUpToOn_pi

@[simp]
theorem hasFtaylorSeriesUpToOn_pi' :
    HasFtaylorSeriesUpToOn n Φ P' s ↔
      ∀ i,
        HasFtaylorSeriesUpToOn n (fun x => Φ x i)
          (fun x m =>
            (@ContinuousLinearMap.proj 𝕜 _ ι F' _ _ _ i).compContinuousMultilinearMap (P' x m))
          s :=
  by
  convert hasFtaylorSeriesUpToOn_pi
  ext
  rfl
#align has_ftaylor_series_up_to_on_pi' hasFtaylorSeriesUpToOn_pi'

theorem contDiffWithinAt_pi :
    ContDiffWithinAt 𝕜 n Φ s x ↔ ∀ i, ContDiffWithinAt 𝕜 n (fun x => Φ x i) s x :=
  by
  set pr := @ContinuousLinearMap.proj 𝕜 _ ι F' _ _ _
  refine' ⟨fun h i => h.continuousLinearMapComp (pr i), fun h m hm => _⟩
  choose u hux p hp using fun i => h i m hm
  exact
    ⟨⋂ i, u i, Filter.interᵢ_mem.2 hux, _,
      hasFtaylorSeriesUpToOn_pi.2 fun i => (hp i).mono <| Inter_subset _ _⟩
#align cont_diff_within_at_pi contDiffWithinAt_pi

theorem contDiffOn_pi : ContDiffOn 𝕜 n Φ s ↔ ∀ i, ContDiffOn 𝕜 n (fun x => Φ x i) s :=
  ⟨fun h i x hx => contDiffWithinAt_pi.1 (h x hx) _, fun h x hx =>
    contDiffWithinAt_pi.2 fun i => h i x hx⟩
#align cont_diff_on_pi contDiffOn_pi

theorem contDiffAt_pi : ContDiffAt 𝕜 n Φ x ↔ ∀ i, ContDiffAt 𝕜 n (fun x => Φ x i) x :=
  contDiffWithinAt_pi
#align cont_diff_at_pi contDiffAt_pi

theorem contDiff_pi : ContDiff 𝕜 n Φ ↔ ∀ i, ContDiff 𝕜 n fun x => Φ x i := by
  simp only [← contDiffOn_univ, contDiffOn_pi]
#align cont_diff_pi contDiff_pi

variable (𝕜 E)

theorem contDiff_apply (i : ι) : ContDiff 𝕜 n fun f : ι → E => f i :=
  contDiff_pi.mp contDiff_id i
#align cont_diff_apply contDiff_apply

theorem contDiff_apply_apply (i : ι) (j : ι') : ContDiff 𝕜 n fun f : ι → ι' → E => f i j :=
  contDiff_pi.mp (contDiff_apply 𝕜 (ι' → E) i) j
#align cont_diff_apply_apply contDiff_apply_apply

variable {𝕜 E}

end Pi

/-! ### Sum of two functions -/

section Add

-- The sum is smooth.
theorem contDiff_add : ContDiff 𝕜 n fun p : F × F => p.1 + p.2 :=
  (IsBoundedLinearMap.fst.add IsBoundedLinearMap.snd).ContDiff
#align cont_diff_add contDiff_add

/-- The sum of two `C^n` functions within a set at a point is `C^n` within this set
at this point. -/
theorem ContDiffWithinAt.add {s : Set E} {f g : E → F} (hf : ContDiffWithinAt 𝕜 n f s x)
    (hg : ContDiffWithinAt 𝕜 n g s x) : ContDiffWithinAt 𝕜 n (fun x => f x + g x) s x :=
  contDiff_add.ContDiffWithinAt.comp x (hf.Prod hg) subset_preimage_univ
#align cont_diff_within_at.add ContDiffWithinAt.add

/-- The sum of two `C^n` functions at a point is `C^n` at this point. -/
theorem ContDiffAt.add {f g : E → F} (hf : ContDiffAt 𝕜 n f x) (hg : ContDiffAt 𝕜 n g x) :
    ContDiffAt 𝕜 n (fun x => f x + g x) x := by
  rw [← contDiffWithinAt_univ] at * <;> exact hf.add hg
#align cont_diff_at.add ContDiffAt.add

/-- The sum of two `C^n`functions is `C^n`. -/
theorem ContDiff.add {f g : E → F} (hf : ContDiff 𝕜 n f) (hg : ContDiff 𝕜 n g) :
    ContDiff 𝕜 n fun x => f x + g x :=
  contDiff_add.comp (hf.Prod hg)
#align cont_diff.add ContDiff.add

/-- The sum of two `C^n` functions on a domain is `C^n`. -/
theorem ContDiffOn.add {s : Set E} {f g : E → F} (hf : ContDiffOn 𝕜 n f s)
    (hg : ContDiffOn 𝕜 n g s) : ContDiffOn 𝕜 n (fun x => f x + g x) s := fun x hx =>
  (hf x hx).add (hg x hx)
#align cont_diff_on.add ContDiffOn.add

variable {i : ℕ}

/-- The iterated derivative of the sum of two functions is the sum of the iterated derivatives.
See also `iterated_fderiv_within_add_apply'`, which uses the spelling `(λ x, f x + g x)`
instead of `f + g`. -/
theorem iteratedFderivWithin_add_apply {f g : E → F} (hf : ContDiffOn 𝕜 i f s)
    (hg : ContDiffOn 𝕜 i g s) (hu : UniqueDiffOn 𝕜 s) (hx : x ∈ s) :
    iteratedFderivWithin 𝕜 i (f + g) s x =
      iteratedFderivWithin 𝕜 i f s x + iteratedFderivWithin 𝕜 i g s x :=
  by
  induction' i with i hi generalizing x
  · ext h
    simp
  · ext h
    have hi' : (i : ℕ∞) < i + 1 := with_top.coe_lt_coe.mpr (Nat.lt_succ_self _)
    have hdf : DifferentiableOn 𝕜 (iteratedFderivWithin 𝕜 i f s) s :=
      hf.differentiable_on_iterated_fderiv_within hi' hu
    have hdg : DifferentiableOn 𝕜 (iteratedFderivWithin 𝕜 i g s) s :=
      hg.differentiable_on_iterated_fderiv_within hi' hu
    have hcdf : ContDiffOn 𝕜 i f s := hf.of_le hi'.le
    have hcdg : ContDiffOn 𝕜 i g s := hg.of_le hi'.le
    calc
      iteratedFderivWithin 𝕜 (i + 1) (f + g) s x h =
          fderivWithin 𝕜 (iteratedFderivWithin 𝕜 i (f + g) s) s x (h 0) (Fin.tail h) :=
        rfl
      _ =
          fderivWithin 𝕜 (iteratedFderivWithin 𝕜 i f s + iteratedFderivWithin 𝕜 i g s) s x (h 0)
            (Fin.tail h) :=
        by
        congr 2
        exact fderivWithin_congr (hu x hx) (fun _ => hi hcdf hcdg) (hi hcdf hcdg hx)
      _ =
          (fderivWithin 𝕜 (iteratedFderivWithin 𝕜 i f s) s +
              fderivWithin 𝕜 (iteratedFderivWithin 𝕜 i g s) s)
            x (h 0) (Fin.tail h) :=
        by rw [Pi.add_def, fderivWithin_add (hu x hx) (hdf x hx) (hdg x hx)] <;> rfl
      _ = (iteratedFderivWithin 𝕜 (i + 1) f s + iteratedFderivWithin 𝕜 (i + 1) g s) x h := rfl

#align iterated_fderiv_within_add_apply iteratedFderivWithin_add_apply

/-- The iterated derivative of the sum of two functions is the sum of the iterated derivatives.
This is the same as `iterated_fderiv_within_add_apply`, but using the spelling `(λ x, f x + g x)`
instead of `f + g`, which can be handy for some rewrites.
TODO: use one form consistently. -/
theorem iteratedFderivWithin_add_apply' {f g : E → F} (hf : ContDiffOn 𝕜 i f s)
    (hg : ContDiffOn 𝕜 i g s) (hu : UniqueDiffOn 𝕜 s) (hx : x ∈ s) :
    iteratedFderivWithin 𝕜 i (fun x => f x + g x) s x =
      iteratedFderivWithin 𝕜 i f s x + iteratedFderivWithin 𝕜 i g s x :=
  iteratedFderivWithin_add_apply hf hg hu hx
#align iterated_fderiv_within_add_apply' iteratedFderivWithin_add_apply'

theorem iteratedFderiv_add_apply {i : ℕ} {f g : E → F} (hf : ContDiff 𝕜 i f) (hg : ContDiff 𝕜 i g) :
    iteratedFderiv 𝕜 i (f + g) x = iteratedFderiv 𝕜 i f x + iteratedFderiv 𝕜 i g x :=
  by
  simp_rw [← contDiffOn_univ, ← iteratedFderivWithin_univ] at hf hg⊢
  exact iteratedFderivWithin_add_apply hf hg uniqueDiffOn_univ (Set.mem_univ _)
#align iterated_fderiv_add_apply iteratedFderiv_add_apply

theorem iteratedFderiv_add_apply' {i : ℕ} {f g : E → F} (hf : ContDiff 𝕜 i f)
    (hg : ContDiff 𝕜 i g) :
    iteratedFderiv 𝕜 i (fun x => f x + g x) x = iteratedFderiv 𝕜 i f x + iteratedFderiv 𝕜 i g x :=
  iteratedFderiv_add_apply hf hg
#align iterated_fderiv_add_apply' iteratedFderiv_add_apply'

end Add

/-! ### Negative -/

section Neg

-- The negative is smooth.
theorem contDiff_neg : ContDiff 𝕜 n fun p : F => -p :=
  IsBoundedLinearMap.id.neg.ContDiff
#align cont_diff_neg contDiff_neg

/-- The negative of a `C^n` function within a domain at a point is `C^n` within this domain at
this point. -/
theorem ContDiffWithinAt.neg {s : Set E} {f : E → F} (hf : ContDiffWithinAt 𝕜 n f s x) :
    ContDiffWithinAt 𝕜 n (fun x => -f x) s x :=
  contDiff_neg.ContDiffWithinAt.comp x hf subset_preimage_univ
#align cont_diff_within_at.neg ContDiffWithinAt.neg

/-- The negative of a `C^n` function at a point is `C^n` at this point. -/
theorem ContDiffAt.neg {f : E → F} (hf : ContDiffAt 𝕜 n f x) : ContDiffAt 𝕜 n (fun x => -f x) x :=
  by rw [← contDiffWithinAt_univ] at * <;> exact hf.neg
#align cont_diff_at.neg ContDiffAt.neg

/-- The negative of a `C^n`function is `C^n`. -/
theorem ContDiff.neg {f : E → F} (hf : ContDiff 𝕜 n f) : ContDiff 𝕜 n fun x => -f x :=
  contDiff_neg.comp hf
#align cont_diff.neg ContDiff.neg

/-- The negative of a `C^n` function on a domain is `C^n`. -/
theorem ContDiffOn.neg {s : Set E} {f : E → F} (hf : ContDiffOn 𝕜 n f s) :
    ContDiffOn 𝕜 n (fun x => -f x) s := fun x hx => (hf x hx).neg
#align cont_diff_on.neg ContDiffOn.neg

variable {i : ℕ}

theorem iteratedFderivWithin_neg_apply {f : E → F} (hu : UniqueDiffOn 𝕜 s) (hx : x ∈ s) :
    iteratedFderivWithin 𝕜 i (-f) s x = -iteratedFderivWithin 𝕜 i f s x :=
  by
  induction' i with i hi generalizing x
  · ext h
    simp
  · ext h
    have hi' : (i : ℕ∞) < i + 1 := with_top.coe_lt_coe.mpr (Nat.lt_succ_self _)
    calc
      iteratedFderivWithin 𝕜 (i + 1) (-f) s x h =
          fderivWithin 𝕜 (iteratedFderivWithin 𝕜 i (-f) s) s x (h 0) (Fin.tail h) :=
        rfl
      _ = fderivWithin 𝕜 (-iteratedFderivWithin 𝕜 i f s) s x (h 0) (Fin.tail h) :=
        by
        congr 2
        exact fderivWithin_congr (hu x hx) (fun _ => hi) (hi hx)
      _ = -(fderivWithin 𝕜 (iteratedFderivWithin 𝕜 i f s) s) x (h 0) (Fin.tail h) := by
        rw [Pi.neg_def, fderivWithin_neg (hu x hx)] <;> rfl
      _ = -(iteratedFderivWithin 𝕜 (i + 1) f s) x h := rfl

#align iterated_fderiv_within_neg_apply iteratedFderivWithin_neg_apply

theorem iteratedFderiv_neg_apply {i : ℕ} {f : E → F} :
    iteratedFderiv 𝕜 i (-f) x = -iteratedFderiv 𝕜 i f x :=
  by
  simp_rw [← iteratedFderivWithin_univ]
  exact iteratedFderivWithin_neg_apply uniqueDiffOn_univ (Set.mem_univ _)
#align iterated_fderiv_neg_apply iteratedFderiv_neg_apply

end Neg

/-! ### Subtraction -/

/-- The difference of two `C^n` functions within a set at a point is `C^n` within this set
at this point. -/
theorem ContDiffWithinAt.sub {s : Set E} {f g : E → F} (hf : ContDiffWithinAt 𝕜 n f s x)
    (hg : ContDiffWithinAt 𝕜 n g s x) : ContDiffWithinAt 𝕜 n (fun x => f x - g x) s x := by
  simpa only [sub_eq_add_neg] using hf.add hg.neg
#align cont_diff_within_at.sub ContDiffWithinAt.sub

/-- The difference of two `C^n` functions at a point is `C^n` at this point. -/
theorem ContDiffAt.sub {f g : E → F} (hf : ContDiffAt 𝕜 n f x) (hg : ContDiffAt 𝕜 n g x) :
    ContDiffAt 𝕜 n (fun x => f x - g x) x := by simpa only [sub_eq_add_neg] using hf.add hg.neg
#align cont_diff_at.sub ContDiffAt.sub

/-- The difference of two `C^n` functions on a domain is `C^n`. -/
theorem ContDiffOn.sub {s : Set E} {f g : E → F} (hf : ContDiffOn 𝕜 n f s)
    (hg : ContDiffOn 𝕜 n g s) : ContDiffOn 𝕜 n (fun x => f x - g x) s := by
  simpa only [sub_eq_add_neg] using hf.add hg.neg
#align cont_diff_on.sub ContDiffOn.sub

/-- The difference of two `C^n` functions is `C^n`. -/
theorem ContDiff.sub {f g : E → F} (hf : ContDiff 𝕜 n f) (hg : ContDiff 𝕜 n g) :
    ContDiff 𝕜 n fun x => f x - g x := by simpa only [sub_eq_add_neg] using hf.add hg.neg
#align cont_diff.sub ContDiff.sub

/-! ### Sum of finitely many functions -/

theorem ContDiffWithinAt.sum {ι : Type _} {f : ι → E → F} {s : Finset ι} {t : Set E} {x : E}
    (h : ∀ i ∈ s, ContDiffWithinAt 𝕜 n (fun x => f i x) t x) :
    ContDiffWithinAt 𝕜 n (fun x => ∑ i in s, f i x) t x := by
  classical
    induction' s using Finset.induction_on with i s is IH
    · simp [contDiffWithinAt_const]
    · simp only [is, Finset.sum_insert, not_false_iff]
      exact
        (h _ (Finset.mem_insert_self i s)).add (IH fun j hj => h _ (Finset.mem_insert_of_mem hj))
#align cont_diff_within_at.sum ContDiffWithinAt.sum

theorem ContDiffAt.sum {ι : Type _} {f : ι → E → F} {s : Finset ι} {x : E}
    (h : ∀ i ∈ s, ContDiffAt 𝕜 n (fun x => f i x) x) :
    ContDiffAt 𝕜 n (fun x => ∑ i in s, f i x) x := by
  rw [← contDiffWithinAt_univ] at * <;> exact ContDiffWithinAt.sum h
#align cont_diff_at.sum ContDiffAt.sum

theorem ContDiffOn.sum {ι : Type _} {f : ι → E → F} {s : Finset ι} {t : Set E}
    (h : ∀ i ∈ s, ContDiffOn 𝕜 n (fun x => f i x) t) :
    ContDiffOn 𝕜 n (fun x => ∑ i in s, f i x) t := fun x hx =>
  ContDiffWithinAt.sum fun i hi => h i hi x hx
#align cont_diff_on.sum ContDiffOn.sum

theorem ContDiff.sum {ι : Type _} {f : ι → E → F} {s : Finset ι}
    (h : ∀ i ∈ s, ContDiff 𝕜 n fun x => f i x) : ContDiff 𝕜 n fun x => ∑ i in s, f i x := by
  simp only [← contDiffOn_univ] at * <;> exact ContDiffOn.sum h
#align cont_diff.sum ContDiff.sum

/-! ### Product of two functions -/

section MulProd

variable {𝔸 𝔸' ι 𝕜' : Type _} [NormedRing 𝔸] [NormedAlgebra 𝕜 𝔸] [NormedCommRing 𝔸']
  [NormedAlgebra 𝕜 𝔸'] [NormedField 𝕜'] [NormedAlgebra 𝕜 𝕜']

-- The product is smooth.
theorem contDiff_mul : ContDiff 𝕜 n fun p : 𝔸 × 𝔸 => p.1 * p.2 :=
  (ContinuousLinearMap.mul 𝕜 𝔸).IsBoundedBilinearMap.ContDiff
#align cont_diff_mul contDiff_mul

/-- The product of two `C^n` functions within a set at a point is `C^n` within this set
at this point. -/
theorem ContDiffWithinAt.mul {s : Set E} {f g : E → 𝔸} (hf : ContDiffWithinAt 𝕜 n f s x)
    (hg : ContDiffWithinAt 𝕜 n g s x) : ContDiffWithinAt 𝕜 n (fun x => f x * g x) s x :=
  contDiff_mul.comp_contDiffWithinAt (hf.Prod hg)
#align cont_diff_within_at.mul ContDiffWithinAt.mul

/-- The product of two `C^n` functions at a point is `C^n` at this point. -/
theorem ContDiffAt.mul {f g : E → 𝔸} (hf : ContDiffAt 𝕜 n f x) (hg : ContDiffAt 𝕜 n g x) :
    ContDiffAt 𝕜 n (fun x => f x * g x) x :=
  hf.mul hg
#align cont_diff_at.mul ContDiffAt.mul

/-- The product of two `C^n` functions on a domain is `C^n`. -/
theorem ContDiffOn.mul {f g : E → 𝔸} (hf : ContDiffOn 𝕜 n f s) (hg : ContDiffOn 𝕜 n g s) :
    ContDiffOn 𝕜 n (fun x => f x * g x) s := fun x hx => (hf x hx).mul (hg x hx)
#align cont_diff_on.mul ContDiffOn.mul

/-- The product of two `C^n`functions is `C^n`. -/
theorem ContDiff.mul {f g : E → 𝔸} (hf : ContDiff 𝕜 n f) (hg : ContDiff 𝕜 n g) :
    ContDiff 𝕜 n fun x => f x * g x :=
  contDiff_mul.comp (hf.Prod hg)
#align cont_diff.mul ContDiff.mul

theorem contDiffWithinAt_prod' {t : Finset ι} {f : ι → E → 𝔸'}
    (h : ∀ i ∈ t, ContDiffWithinAt 𝕜 n (f i) s x) : ContDiffWithinAt 𝕜 n (∏ i in t, f i) s x :=
  Finset.prod_induction f (fun f => ContDiffWithinAt 𝕜 n f s x) (fun _ _ => ContDiffWithinAt.mul)
    (@contDiffWithinAt_const _ _ _ _ _ _ _ _ _ _ _ 1) h
#align cont_diff_within_at_prod' contDiffWithinAt_prod'

theorem contDiffWithinAt_prod {t : Finset ι} {f : ι → E → 𝔸'}
    (h : ∀ i ∈ t, ContDiffWithinAt 𝕜 n (f i) s x) :
    ContDiffWithinAt 𝕜 n (fun y => ∏ i in t, f i y) s x := by
  simpa only [← Finset.prod_apply] using contDiffWithinAt_prod' h
#align cont_diff_within_at_prod contDiffWithinAt_prod

theorem contDiffAt_prod' {t : Finset ι} {f : ι → E → 𝔸'} (h : ∀ i ∈ t, ContDiffAt 𝕜 n (f i) x) :
    ContDiffAt 𝕜 n (∏ i in t, f i) x :=
  contDiffWithinAt_prod' h
#align cont_diff_at_prod' contDiffAt_prod'

theorem contDiffAt_prod {t : Finset ι} {f : ι → E → 𝔸'} (h : ∀ i ∈ t, ContDiffAt 𝕜 n (f i) x) :
    ContDiffAt 𝕜 n (fun y => ∏ i in t, f i y) x :=
  contDiffWithinAt_prod h
#align cont_diff_at_prod contDiffAt_prod

theorem contDiffOn_prod' {t : Finset ι} {f : ι → E → 𝔸'} (h : ∀ i ∈ t, ContDiffOn 𝕜 n (f i) s) :
    ContDiffOn 𝕜 n (∏ i in t, f i) s := fun x hx => contDiffWithinAt_prod' fun i hi => h i hi x hx
#align cont_diff_on_prod' contDiffOn_prod'

theorem contDiffOn_prod {t : Finset ι} {f : ι → E → 𝔸'} (h : ∀ i ∈ t, ContDiffOn 𝕜 n (f i) s) :
    ContDiffOn 𝕜 n (fun y => ∏ i in t, f i y) s := fun x hx =>
  contDiffWithinAt_prod fun i hi => h i hi x hx
#align cont_diff_on_prod contDiffOn_prod

theorem contDiff_prod' {t : Finset ι} {f : ι → E → 𝔸'} (h : ∀ i ∈ t, ContDiff 𝕜 n (f i)) :
    ContDiff 𝕜 n (∏ i in t, f i) :=
  contDiff_iff_contDiffAt.mpr fun x => contDiffAt_prod' fun i hi => (h i hi).ContDiffAt
#align cont_diff_prod' contDiff_prod'

theorem contDiff_prod {t : Finset ι} {f : ι → E → 𝔸'} (h : ∀ i ∈ t, ContDiff 𝕜 n (f i)) :
    ContDiff 𝕜 n fun y => ∏ i in t, f i y :=
  contDiff_iff_contDiffAt.mpr fun x => contDiffAt_prod fun i hi => (h i hi).ContDiffAt
#align cont_diff_prod contDiff_prod

theorem ContDiff.pow {f : E → 𝔸} (hf : ContDiff 𝕜 n f) : ∀ m : ℕ, ContDiff 𝕜 n fun x => f x ^ m
  | 0 => by simpa using contDiff_const
  | m + 1 => by simpa [pow_succ] using hf.mul (ContDiff.pow m)
#align cont_diff.pow ContDiff.pow

theorem ContDiffWithinAt.pow {f : E → 𝔸} (hf : ContDiffWithinAt 𝕜 n f s x) (m : ℕ) :
    ContDiffWithinAt 𝕜 n (fun y => f y ^ m) s x :=
  (contDiff_id.pow m).comp_contDiffWithinAt hf
#align cont_diff_within_at.pow ContDiffWithinAt.pow

theorem ContDiffAt.pow {f : E → 𝔸} (hf : ContDiffAt 𝕜 n f x) (m : ℕ) :
    ContDiffAt 𝕜 n (fun y => f y ^ m) x :=
  hf.pow m
#align cont_diff_at.pow ContDiffAt.pow

theorem ContDiffOn.pow {f : E → 𝔸} (hf : ContDiffOn 𝕜 n f s) (m : ℕ) :
    ContDiffOn 𝕜 n (fun y => f y ^ m) s := fun y hy => (hf y hy).pow m
#align cont_diff_on.pow ContDiffOn.pow

theorem ContDiffWithinAt.div_const {f : E → 𝕜'} {n} (hf : ContDiffWithinAt 𝕜 n f s x) (c : 𝕜') :
    ContDiffWithinAt 𝕜 n (fun x => f x / c) s x := by
  simpa only [div_eq_mul_inv] using hf.mul contDiffWithinAt_const
#align cont_diff_within_at.div_const ContDiffWithinAt.div_const

theorem ContDiffAt.div_const {f : E → 𝕜'} {n} (hf : ContDiffAt 𝕜 n f x) (c : 𝕜') :
    ContDiffAt 𝕜 n (fun x => f x / c) x :=
  hf.div_const c
#align cont_diff_at.div_const ContDiffAt.div_const

theorem ContDiffOn.div_const {f : E → 𝕜'} {n} (hf : ContDiffOn 𝕜 n f s) (c : 𝕜') :
    ContDiffOn 𝕜 n (fun x => f x / c) s := fun x hx => (hf x hx).div_const c
#align cont_diff_on.div_const ContDiffOn.div_const

theorem ContDiff.div_const {f : E → 𝕜'} {n} (hf : ContDiff 𝕜 n f) (c : 𝕜') :
    ContDiff 𝕜 n fun x => f x / c := by simpa only [div_eq_mul_inv] using hf.mul contDiff_const
#align cont_diff.div_const ContDiff.div_const

end MulProd

/-! ### Scalar multiplication -/

section Smul

-- The scalar multiplication is smooth.
theorem contDiff_smul : ContDiff 𝕜 n fun p : 𝕜 × F => p.1 • p.2 :=
  isBoundedBilinearMapSmul.ContDiff
#align cont_diff_smul contDiff_smul

/-- The scalar multiplication of two `C^n` functions within a set at a point is `C^n` within this
set at this point. -/
theorem ContDiffWithinAt.smul {s : Set E} {f : E → 𝕜} {g : E → F} (hf : ContDiffWithinAt 𝕜 n f s x)
    (hg : ContDiffWithinAt 𝕜 n g s x) : ContDiffWithinAt 𝕜 n (fun x => f x • g x) s x :=
  contDiff_smul.ContDiffWithinAt.comp x (hf.Prod hg) subset_preimage_univ
#align cont_diff_within_at.smul ContDiffWithinAt.smul

/-- The scalar multiplication of two `C^n` functions at a point is `C^n` at this point. -/
theorem ContDiffAt.smul {f : E → 𝕜} {g : E → F} (hf : ContDiffAt 𝕜 n f x)
    (hg : ContDiffAt 𝕜 n g x) : ContDiffAt 𝕜 n (fun x => f x • g x) x := by
  rw [← contDiffWithinAt_univ] at * <;> exact hf.smul hg
#align cont_diff_at.smul ContDiffAt.smul

/-- The scalar multiplication of two `C^n` functions is `C^n`. -/
theorem ContDiff.smul {f : E → 𝕜} {g : E → F} (hf : ContDiff 𝕜 n f) (hg : ContDiff 𝕜 n g) :
    ContDiff 𝕜 n fun x => f x • g x :=
  contDiff_smul.comp (hf.Prod hg)
#align cont_diff.smul ContDiff.smul

/-- The scalar multiplication of two `C^n` functions on a domain is `C^n`. -/
theorem ContDiffOn.smul {s : Set E} {f : E → 𝕜} {g : E → F} (hf : ContDiffOn 𝕜 n f s)
    (hg : ContDiffOn 𝕜 n g s) : ContDiffOn 𝕜 n (fun x => f x • g x) s := fun x hx =>
  (hf x hx).smul (hg x hx)
#align cont_diff_on.smul ContDiffOn.smul

end Smul

/-! ### Constant scalar multiplication -/

section ConstSmul

variable {R : Type _} [Semiring R] [Module R F] [SMulCommClass 𝕜 R F]

variable [ContinuousConstSMul R F]

-- The scalar multiplication with a constant is smooth.
theorem contDiff_const_smul (c : R) : ContDiff 𝕜 n fun p : F => c • p :=
  (c • ContinuousLinearMap.id 𝕜 F).ContDiff
#align cont_diff_const_smul contDiff_const_smul

/-- The scalar multiplication of a constant and a `C^n` function within a set at a point is `C^n`
within this set at this point. -/
theorem ContDiffWithinAt.const_smul {s : Set E} {f : E → F} {x : E} (c : R)
    (hf : ContDiffWithinAt 𝕜 n f s x) : ContDiffWithinAt 𝕜 n (fun y => c • f y) s x :=
  (contDiff_const_smul c).ContDiffAt.comp_contDiffWithinAt x hf
#align cont_diff_within_at.const_smul ContDiffWithinAt.const_smul

/-- The scalar multiplication of a constant and a `C^n` function at a point is `C^n` at this
point. -/
theorem ContDiffAt.const_smul {f : E → F} {x : E} (c : R) (hf : ContDiffAt 𝕜 n f x) :
    ContDiffAt 𝕜 n (fun y => c • f y) x := by
  rw [← contDiffWithinAt_univ] at * <;> exact hf.const_smul c
#align cont_diff_at.const_smul ContDiffAt.const_smul

/-- The scalar multiplication of a constant and a `C^n` function is `C^n`. -/
theorem ContDiff.const_smul {f : E → F} (c : R) (hf : ContDiff 𝕜 n f) :
    ContDiff 𝕜 n fun y => c • f y :=
  (contDiff_const_smul c).comp hf
#align cont_diff.const_smul ContDiff.const_smul

/-- The scalar multiplication of a constant and a `C^n` on a domain is `C^n`. -/
theorem ContDiffOn.const_smul {s : Set E} {f : E → F} (c : R) (hf : ContDiffOn 𝕜 n f s) :
    ContDiffOn 𝕜 n (fun y => c • f y) s := fun x hx => (hf x hx).const_smul c
#align cont_diff_on.const_smul ContDiffOn.const_smul

variable {i : ℕ} {a : R}

theorem iteratedFderivWithin_const_smul_apply (hf : ContDiffOn 𝕜 i f s) (hu : UniqueDiffOn 𝕜 s)
    (hx : x ∈ s) : iteratedFderivWithin 𝕜 i (a • f) s x = a • iteratedFderivWithin 𝕜 i f s x :=
  by
  induction' i with i hi generalizing x
  · ext
    simp
  · ext h
    have hi' : (i : ℕ∞) < i + 1 := with_top.coe_lt_coe.mpr (Nat.lt_succ_self _)
    have hdf : DifferentiableOn 𝕜 (iteratedFderivWithin 𝕜 i f s) s :=
      hf.differentiable_on_iterated_fderiv_within hi' hu
    have hcdf : ContDiffOn 𝕜 i f s := hf.of_le hi'.le
    calc
      iteratedFderivWithin 𝕜 (i + 1) (a • f) s x h =
          fderivWithin 𝕜 (iteratedFderivWithin 𝕜 i (a • f) s) s x (h 0) (Fin.tail h) :=
        rfl
      _ = fderivWithin 𝕜 (a • iteratedFderivWithin 𝕜 i f s) s x (h 0) (Fin.tail h) :=
        by
        congr 2
        exact fderivWithin_congr (hu x hx) (fun _ => hi hcdf) (hi hcdf hx)
      _ = (a • fderivWithin 𝕜 (iteratedFderivWithin 𝕜 i f s)) s x (h 0) (Fin.tail h) := by
        rw [Pi.smul_def, fderivWithin_const_smul (hu x hx) (hdf x hx)] <;> rfl
      _ = a • iteratedFderivWithin 𝕜 (i + 1) f s x h := rfl

#align iterated_fderiv_within_const_smul_apply iteratedFderivWithin_const_smul_apply

theorem iteratedFderiv_const_smul_apply {x : E} (hf : ContDiff 𝕜 i f) :
    iteratedFderiv 𝕜 i (a • f) x = a • iteratedFderiv 𝕜 i f x :=
  by
  simp_rw [← contDiffOn_univ, ← iteratedFderivWithin_univ] at *
  refine' iteratedFderivWithin_const_smul_apply hf uniqueDiffOn_univ (Set.mem_univ _)
#align iterated_fderiv_const_smul_apply iteratedFderiv_const_smul_apply

end ConstSmul

/-! ### Cartesian product of two functions -/

section Prod_map

variable {E' : Type _} [NormedAddCommGroup E'] [NormedSpace 𝕜 E']

variable {F' : Type _} [NormedAddCommGroup F'] [NormedSpace 𝕜 F']

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- The product map of two `C^n` functions within a set at a point is `C^n`
within the product set at the product point. -/
theorem ContDiffWithinAt.prod_map' {s : Set E} {t : Set E'} {f : E → F} {g : E' → F'} {p : E × E'}
    (hf : ContDiffWithinAt 𝕜 n f s p.1) (hg : ContDiffWithinAt 𝕜 n g t p.2) :
    ContDiffWithinAt 𝕜 n (Prod.map f g) (s ×ˢ t) p :=
  (hf.comp p contDiffWithinAt_fst (prod_subset_preimage_fst _ _)).Prod
    (hg.comp p contDiffWithinAt_snd (prod_subset_preimage_snd _ _))
#align cont_diff_within_at.prod_map' ContDiffWithinAt.prod_map'

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem ContDiffWithinAt.prod_map {s : Set E} {t : Set E'} {f : E → F} {g : E' → F'} {x : E}
    {y : E'} (hf : ContDiffWithinAt 𝕜 n f s x) (hg : ContDiffWithinAt 𝕜 n g t y) :
    ContDiffWithinAt 𝕜 n (Prod.map f g) (s ×ˢ t) (x, y) :=
  ContDiffWithinAt.prod_map' hf hg
#align cont_diff_within_at.prod_map ContDiffWithinAt.prod_map

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- The product map of two `C^n` functions on a set is `C^n` on the product set. -/
theorem ContDiffOn.prod_map {E' : Type _} [NormedAddCommGroup E'] [NormedSpace 𝕜 E'] {F' : Type _}
    [NormedAddCommGroup F'] [NormedSpace 𝕜 F'] {s : Set E} {t : Set E'} {f : E → F} {g : E' → F'}
    (hf : ContDiffOn 𝕜 n f s) (hg : ContDiffOn 𝕜 n g t) : ContDiffOn 𝕜 n (Prod.map f g) (s ×ˢ t) :=
  (hf.comp contDiffOn_fst (prod_subset_preimage_fst _ _)).Prod
    (hg.comp contDiffOn_snd (prod_subset_preimage_snd _ _))
#align cont_diff_on.prod_map ContDiffOn.prod_map

/-- The product map of two `C^n` functions within a set at a point is `C^n`
within the product set at the product point. -/
theorem ContDiffAt.prod_map {f : E → F} {g : E' → F'} {x : E} {y : E'} (hf : ContDiffAt 𝕜 n f x)
    (hg : ContDiffAt 𝕜 n g y) : ContDiffAt 𝕜 n (Prod.map f g) (x, y) :=
  by
  rw [ContDiffAt] at *
  convert hf.prod_map hg
  simp only [univ_prod_univ]
#align cont_diff_at.prod_map ContDiffAt.prod_map

/-- The product map of two `C^n` functions within a set at a point is `C^n`
within the product set at the product point. -/
theorem ContDiffAt.prod_map' {f : E → F} {g : E' → F'} {p : E × E'} (hf : ContDiffAt 𝕜 n f p.1)
    (hg : ContDiffAt 𝕜 n g p.2) : ContDiffAt 𝕜 n (Prod.map f g) p :=
  by
  rcases p with ⟨⟩
  exact ContDiffAt.prod_map hf hg
#align cont_diff_at.prod_map' ContDiffAt.prod_map'

/-- The product map of two `C^n` functions is `C^n`. -/
theorem ContDiff.prod_map {f : E → F} {g : E' → F'} (hf : ContDiff 𝕜 n f) (hg : ContDiff 𝕜 n g) :
    ContDiff 𝕜 n (Prod.map f g) :=
  by
  rw [contDiff_iff_contDiffAt] at *
  exact fun ⟨x, y⟩ => (hf x).Prod_map (hg y)
#align cont_diff.prod_map ContDiff.prod_map

theorem contDiff_prod_mk_left (f₀ : F) : ContDiff 𝕜 n fun e : E => (e, f₀) :=
  contDiff_id.Prod contDiff_const
#align cont_diff_prod_mk_left contDiff_prod_mk_left

theorem contDiff_prod_mk_right (e₀ : E) : ContDiff 𝕜 n fun f : F => (e₀, f) :=
  contDiff_const.Prod contDiff_id
#align cont_diff_prod_mk_right contDiff_prod_mk_right

end Prod_map

/-! ### Inversion in a complete normed algebra -/

section AlgebraInverse

variable (𝕜) {R : Type _} [NormedRing R] [NormedAlgebra 𝕜 R]

open NormedRing ContinuousLinearMap Ring

/-- In a complete normed algebra, the operation of inversion is `C^n`, for all `n`, at each
invertible element.  The proof is by induction, bootstrapping using an identity expressing the
derivative of inversion as a bilinear map of inversion itself. -/
theorem contDiffAt_ring_inverse [CompleteSpace R] (x : Rˣ) : ContDiffAt 𝕜 n Ring.inverse (x : R) :=
  by
  induction' n using ENat.nat_induction with n IH Itop
  · intro m hm
    refine' ⟨{ y : R | IsUnit y }, _, _⟩
    · simp [nhdsWithin_univ]
      exact x.nhds
    · use ftaylorSeriesWithin 𝕜 inverse univ
      rw [le_antisymm hm bot_le, hasFtaylorSeriesUpToOn_zero_iff]
      constructor
      · rintro _ ⟨x', rfl⟩
        exact (inverse_continuous_at x').ContinuousWithinAt
      · simp [ftaylorSeriesWithin]
  · apply cont_diff_at_succ_iff_has_fderiv_at.mpr
    refine' ⟨fun x : R => -mul_left_right 𝕜 R (inverse x) (inverse x), _, _⟩
    · refine' ⟨{ y : R | IsUnit y }, x.nhds, _⟩
      rintro _ ⟨y, rfl⟩
      rw [inverse_unit]
      exact hasFderivAt_ring_inverse y
    ·
      convert(mul_left_right_is_bounded_bilinear 𝕜 R).ContDiff.neg.comp_contDiffAt (x : R)
          (IH.prod IH)
  · exact cont_diff_at_top.mpr Itop
#align cont_diff_at_ring_inverse contDiffAt_ring_inverse

variable (𝕜) {𝕜' : Type _} [NormedField 𝕜'] [NormedAlgebra 𝕜 𝕜'] [CompleteSpace 𝕜']

theorem contDiffAt_inv {x : 𝕜'} (hx : x ≠ 0) {n} : ContDiffAt 𝕜 n Inv.inv x := by
  simpa only [Ring.inverse_eq_inv'] using contDiffAt_ring_inverse 𝕜 (Units.mk0 x hx)
#align cont_diff_at_inv contDiffAt_inv

theorem contDiffOn_inv {n} : ContDiffOn 𝕜 n (Inv.inv : 𝕜' → 𝕜') ({0}ᶜ) := fun x hx =>
  (contDiffAt_inv 𝕜 hx).ContDiffWithinAt
#align cont_diff_on_inv contDiffOn_inv

variable {𝕜}

-- TODO: the next few lemmas don't need `𝕜` or `𝕜'` to be complete
-- A good way to show this is to generalize `cont_diff_at_ring_inverse` to the setting
-- of a function `f` such that `∀ᶠ x in 𝓝 a, x * f x = 1`.
theorem ContDiffWithinAt.inv {f : E → 𝕜'} {n} (hf : ContDiffWithinAt 𝕜 n f s x) (hx : f x ≠ 0) :
    ContDiffWithinAt 𝕜 n (fun x => (f x)⁻¹) s x :=
  (contDiffAt_inv 𝕜 hx).comp_contDiffWithinAt x hf
#align cont_diff_within_at.inv ContDiffWithinAt.inv

theorem ContDiffOn.inv {f : E → 𝕜'} {n} (hf : ContDiffOn 𝕜 n f s) (h : ∀ x ∈ s, f x ≠ 0) :
    ContDiffOn 𝕜 n (fun x => (f x)⁻¹) s := fun x hx => (hf.ContDiffWithinAt hx).inv (h x hx)
#align cont_diff_on.inv ContDiffOn.inv

theorem ContDiffAt.inv {f : E → 𝕜'} {n} (hf : ContDiffAt 𝕜 n f x) (hx : f x ≠ 0) :
    ContDiffAt 𝕜 n (fun x => (f x)⁻¹) x :=
  hf.inv hx
#align cont_diff_at.inv ContDiffAt.inv

theorem ContDiff.inv {f : E → 𝕜'} {n} (hf : ContDiff 𝕜 n f) (h : ∀ x, f x ≠ 0) :
    ContDiff 𝕜 n fun x => (f x)⁻¹ :=
  by
  rw [contDiff_iff_contDiffAt]
  exact fun x => hf.cont_diff_at.inv (h x)
#align cont_diff.inv ContDiff.inv

-- TODO: generalize to `f g : E → 𝕜'`
theorem ContDiffWithinAt.div [CompleteSpace 𝕜] {f g : E → 𝕜} {n} (hf : ContDiffWithinAt 𝕜 n f s x)
    (hg : ContDiffWithinAt 𝕜 n g s x) (hx : g x ≠ 0) :
    ContDiffWithinAt 𝕜 n (fun x => f x / g x) s x := by
  simpa only [div_eq_mul_inv] using hf.mul (hg.inv hx)
#align cont_diff_within_at.div ContDiffWithinAt.div

theorem ContDiffOn.div [CompleteSpace 𝕜] {f g : E → 𝕜} {n} (hf : ContDiffOn 𝕜 n f s)
    (hg : ContDiffOn 𝕜 n g s) (h₀ : ∀ x ∈ s, g x ≠ 0) : ContDiffOn 𝕜 n (f / g) s := fun x hx =>
  (hf x hx).div (hg x hx) (h₀ x hx)
#align cont_diff_on.div ContDiffOn.div

theorem ContDiffAt.div [CompleteSpace 𝕜] {f g : E → 𝕜} {n} (hf : ContDiffAt 𝕜 n f x)
    (hg : ContDiffAt 𝕜 n g x) (hx : g x ≠ 0) : ContDiffAt 𝕜 n (fun x => f x / g x) x :=
  hf.div hg hx
#align cont_diff_at.div ContDiffAt.div

theorem ContDiff.div [CompleteSpace 𝕜] {f g : E → 𝕜} {n} (hf : ContDiff 𝕜 n f) (hg : ContDiff 𝕜 n g)
    (h0 : ∀ x, g x ≠ 0) : ContDiff 𝕜 n fun x => f x / g x :=
  by
  simp only [contDiff_iff_contDiffAt] at *
  exact fun x => (hf x).div (hg x) (h0 x)
#align cont_diff.div ContDiff.div

end AlgebraInverse

/-! ### Inversion of continuous linear maps between Banach spaces -/

section MapInverse

open ContinuousLinearMap

/-- At a continuous linear equivalence `e : E ≃L[𝕜] F` between Banach spaces, the operation of
inversion is `C^n`, for all `n`. -/
theorem contDiffAt_map_inverse [CompleteSpace E] (e : E ≃L[𝕜] F) :
    ContDiffAt 𝕜 n inverse (e : E →L[𝕜] F) :=
  by
  nontriviality E
  -- first, we use the lemma `to_ring_inverse` to rewrite in terms of `ring.inverse` in the ring
  -- `E →L[𝕜] E`
  let O₁ : (E →L[𝕜] E) → F →L[𝕜] E := fun f => f.comp (e.symm : F →L[𝕜] E)
  let O₂ : (E →L[𝕜] F) → E →L[𝕜] E := fun f => (e.symm : F →L[𝕜] E).comp f
  have : ContinuousLinearMap.inverse = O₁ ∘ Ring.inverse ∘ O₂ := funext (to_ring_inverse e)
  rw [this]
  -- `O₁` and `O₂` are `cont_diff`,
  -- so we reduce to proving that `ring.inverse` is `cont_diff`
  have h₁ : ContDiff 𝕜 n O₁ := cont_diff_id.clm_comp contDiff_const
  have h₂ : ContDiff 𝕜 n O₂ := cont_diff_const.clm_comp contDiff_id
  refine' h₁.cont_diff_at.comp _ (ContDiffAt.comp _ _ h₂.cont_diff_at)
  convert contDiffAt_ring_inverse 𝕜 (1 : (E →L[𝕜] E)ˣ)
  simp [O₂, one_def]
#align cont_diff_at_map_inverse contDiffAt_map_inverse

end MapInverse

section FunctionInverse

open ContinuousLinearMap

/-- If `f` is a local homeomorphism and the point `a` is in its target,
and if `f` is `n` times continuously differentiable at `f.symm a`,
and if the derivative at `f.symm a` is a continuous linear equivalence,
then `f.symm` is `n` times continuously differentiable at the point `a`.

This is one of the easy parts of the inverse function theorem: it assumes that we already have
an inverse function. -/
theorem LocalHomeomorph.contDiffAt_symm [CompleteSpace E] (f : LocalHomeomorph E F)
    {f₀' : E ≃L[𝕜] F} {a : F} (ha : a ∈ f.target)
    (hf₀' : HasFderivAt f (f₀' : E →L[𝕜] F) (f.symm a)) (hf : ContDiffAt 𝕜 n f (f.symm a)) :
    ContDiffAt 𝕜 n f.symm a :=
  by
  -- We prove this by induction on `n`
  induction' n using ENat.nat_induction with n IH Itop
  · rw [contDiffAt_zero]
    exact ⟨f.target, IsOpen.mem_nhds f.open_target ha, f.continuous_inv_fun⟩
  · obtain ⟨f', ⟨u, hu, hff'⟩, hf'⟩ := cont_diff_at_succ_iff_has_fderiv_at.mp hf
    apply cont_diff_at_succ_iff_has_fderiv_at.mpr
    -- For showing `n.succ` times continuous differentiability (the main inductive step), it
    -- suffices to produce the derivative and show that it is `n` times continuously differentiable
    have eq_f₀' : f' (f.symm a) = f₀' := (hff' (f.symm a) (mem_of_mem_nhds hu)).unique hf₀'
    -- This follows by a bootstrapping formula expressing the derivative as a function of `f` itself
    refine' ⟨inverse ∘ f' ∘ f.symm, _, _⟩
    · -- We first check that the derivative of `f` is that formula
      have h_nhds : { y : E | ∃ e : E ≃L[𝕜] F, ↑e = f' y } ∈ 𝓝 (f.symm a) :=
        by
        have hf₀' := f₀'.nhds
        rw [← eq_f₀'] at hf₀'
        exact hf'.continuous_at.preimage_mem_nhds hf₀'
      obtain ⟨t, htu, ht, htf⟩ := mem_nhds_iff.mp (Filter.inter_mem hu h_nhds)
      use f.target ∩ f.symm ⁻¹' t
      refine' ⟨IsOpen.mem_nhds _ _, _⟩
      · exact f.preimage_open_of_open_symm ht
      · exact mem_inter ha (mem_preimage.mpr htf)
      intro x hx
      obtain ⟨hxu, e, he⟩ := htu hx.2
      have h_deriv : HasFderivAt f (↑e) (f.symm x) :=
        by
        rw [he]
        exact hff' (f.symm x) hxu
      convert f.has_fderiv_at_symm hx.1 h_deriv
      simp [← he]
    · -- Then we check that the formula, being a composition of `cont_diff` pieces, is
      -- itself `cont_diff`
      have h_deriv₁ : ContDiffAt 𝕜 n inverse (f' (f.symm a)) :=
        by
        rw [eq_f₀']
        exact contDiffAt_map_inverse _
      have h_deriv₂ : ContDiffAt 𝕜 n f.symm a :=
        by
        refine' IH (hf.of_le _)
        norm_cast
        exact Nat.le_succ n
      exact (h_deriv₁.comp _ hf').comp _ h_deriv₂
  · refine' cont_diff_at_top.mpr _
    intro n
    exact Itop n (cont_diff_at_top.mp hf n)
#align local_homeomorph.cont_diff_at_symm LocalHomeomorph.contDiffAt_symm

/-- If `f` is an `n` times continuously differentiable homeomorphism,
and if the derivative of `f` at each point is a continuous linear equivalence,
then `f.symm` is `n` times continuously differentiable.

This is one of the easy parts of the inverse function theorem: it assumes that we already have
an inverse function. -/
theorem Homeomorph.contDiff_symm [CompleteSpace E] (f : E ≃ₜ F) {f₀' : E → E ≃L[𝕜] F}
    (hf₀' : ∀ a, HasFderivAt f (f₀' a : E →L[𝕜] F) a) (hf : ContDiff 𝕜 n (f : E → F)) :
    ContDiff 𝕜 n (f.symm : F → E) :=
  contDiff_iff_contDiffAt.2 fun x =>
    f.toLocalHomeomorph.contDiffAt_symm (mem_univ x) (hf₀' _) hf.ContDiffAt
#align homeomorph.cont_diff_symm Homeomorph.contDiff_symm

/-- Let `f` be a local homeomorphism of a nontrivially normed field, let `a` be a point in its
target. if `f` is `n` times continuously differentiable at `f.symm a`, and if the derivative at
`f.symm a` is nonzero, then `f.symm` is `n` times continuously differentiable at the point `a`.

This is one of the easy parts of the inverse function theorem: it assumes that we already have
an inverse function. -/
theorem LocalHomeomorph.contDiffAt_symm_deriv [CompleteSpace 𝕜] (f : LocalHomeomorph 𝕜 𝕜)
    {f₀' a : 𝕜} (h₀ : f₀' ≠ 0) (ha : a ∈ f.target) (hf₀' : HasDerivAt f f₀' (f.symm a))
    (hf : ContDiffAt 𝕜 n f (f.symm a)) : ContDiffAt 𝕜 n f.symm a :=
  f.contDiffAt_symm ha (hf₀'.hasFderivAt_equiv h₀) hf
#align local_homeomorph.cont_diff_at_symm_deriv LocalHomeomorph.contDiffAt_symm_deriv

/-- Let `f` be an `n` times continuously differentiable homeomorphism of a nontrivially normed
field.  Suppose that the derivative of `f` is never equal to zero. Then `f.symm` is `n` times
continuously differentiable.

This is one of the easy parts of the inverse function theorem: it assumes that we already have
an inverse function. -/
theorem Homeomorph.contDiff_symm_deriv [CompleteSpace 𝕜] (f : 𝕜 ≃ₜ 𝕜) {f' : 𝕜 → 𝕜}
    (h₀ : ∀ x, f' x ≠ 0) (hf' : ∀ x, HasDerivAt f (f' x) x) (hf : ContDiff 𝕜 n (f : 𝕜 → 𝕜)) :
    ContDiff 𝕜 n (f.symm : 𝕜 → 𝕜) :=
  contDiff_iff_contDiffAt.2 fun x =>
    f.toLocalHomeomorph.contDiffAt_symm_deriv (h₀ _) (mem_univ x) (hf' _) hf.ContDiffAt
#align homeomorph.cont_diff_symm_deriv Homeomorph.contDiff_symm_deriv

end FunctionInverse

/-! ### Finite dimensional results -/

section FiniteDimensional

open Function FiniteDimensional

variable [CompleteSpace 𝕜]

/-- A family of continuous linear maps is `C^n` on `s` if all its applications are. -/
theorem contDiffOn_clm_apply {n : ℕ∞} {f : E → F →L[𝕜] G} {s : Set E} [FiniteDimensional 𝕜 F] :
    ContDiffOn 𝕜 n f s ↔ ∀ y, ContDiffOn 𝕜 n (fun x => f x y) s :=
  by
  refine' ⟨fun h y => h.clm_apply contDiffOn_const, fun h => _⟩
  let d := finrank 𝕜 F
  have hd : d = finrank 𝕜 (Fin d → 𝕜) := (finrank_fin_fun 𝕜).symm
  let e₁ := ContinuousLinearEquiv.ofFinrankEq hd
  let e₂ := (e₁.arrow_congr (1 : G ≃L[𝕜] G)).trans (ContinuousLinearEquiv.piRing (Fin d))
  rw [← comp.left_id f, ← e₂.symm_comp_self]
  exact e₂.symm.cont_diff.comp_cont_diff_on (cont_diff_on_pi.mpr fun i => h _)
#align cont_diff_on_clm_apply contDiffOn_clm_apply

theorem contDiff_clm_apply_iff {n : ℕ∞} {f : E → F →L[𝕜] G} [FiniteDimensional 𝕜 F] :
    ContDiff 𝕜 n f ↔ ∀ y, ContDiff 𝕜 n fun x => f x y := by
  simp_rw [← contDiffOn_univ, contDiffOn_clm_apply]
#align cont_diff_clm_apply_iff contDiff_clm_apply_iff

/-- This is a useful lemma to prove that a certain operation preserves functions being `C^n`.
When you do induction on `n`, this gives a useful characterization of a function being `C^(n+1)`,
assuming you have already computed the derivative. The advantage of this version over
`cont_diff_succ_iff_fderiv` is that both occurences of `cont_diff` are for functions with the same
domain and codomain (`E` and `F`). This is not the case for `cont_diff_succ_iff_fderiv`, which
often requires an inconvenient need to generalize `F`, which results in universe issues
(see the discussion in the section of `cont_diff.comp`).

This lemma avoids these universe issues, but only applies for finite dimensional `E`. -/
theorem contDiff_succ_iff_fderiv_apply [FiniteDimensional 𝕜 E] {n : ℕ} {f : E → F} :
    ContDiff 𝕜 (n + 1 : ℕ) f ↔ Differentiable 𝕜 f ∧ ∀ y, ContDiff 𝕜 n fun x => fderiv 𝕜 f x y := by
  rw [contDiff_succ_iff_fderiv, contDiff_clm_apply_iff]
#align cont_diff_succ_iff_fderiv_apply contDiff_succ_iff_fderiv_apply

theorem contDiffOn_succ_of_fderiv_apply [FiniteDimensional 𝕜 E] {n : ℕ} {f : E → F} {s : Set E}
    (hf : DifferentiableOn 𝕜 f s) (h : ∀ y, ContDiffOn 𝕜 n (fun x => fderivWithin 𝕜 f s x y) s) :
    ContDiffOn 𝕜 (n + 1 : ℕ) f s :=
  contDiffOn_succ_of_fderivWithin hf <| contDiffOn_clm_apply.mpr h
#align cont_diff_on_succ_of_fderiv_apply contDiffOn_succ_of_fderiv_apply

theorem contDiffOn_succ_iff_fderiv_apply [FiniteDimensional 𝕜 E] {n : ℕ} {f : E → F} {s : Set E}
    (hs : UniqueDiffOn 𝕜 s) :
    ContDiffOn 𝕜 (n + 1 : ℕ) f s ↔
      DifferentiableOn 𝕜 f s ∧ ∀ y, ContDiffOn 𝕜 n (fun x => fderivWithin 𝕜 f s x y) s :=
  by rw [contDiffOn_succ_iff_fderivWithin hs, contDiffOn_clm_apply]
#align cont_diff_on_succ_iff_fderiv_apply contDiffOn_succ_iff_fderiv_apply

end FiniteDimensional

section Real

/-!
### Results over `ℝ` or `ℂ`
  The results in this section rely on the Mean Value Theorem, and therefore hold only over `ℝ` (and
  its extension fields such as `ℂ`).
-/

variable {𝕂 : Type _} [IsROrC 𝕂] {E' : Type _} [NormedAddCommGroup E'] [NormedSpace 𝕂 E']
  {F' : Type _} [NormedAddCommGroup F'] [NormedSpace 𝕂 F']

/-- If a function has a Taylor series at order at least 1, then at points in the interior of the
    domain of definition, the term of order 1 of this series is a strict derivative of `f`. -/
theorem HasFtaylorSeriesUpToOn.hasStrictFderivAt {s : Set E'} {f : E' → F'} {x : E'}
    {p : E' → FormalMultilinearSeries 𝕂 E' F'} (hf : HasFtaylorSeriesUpToOn n f p s) (hn : 1 ≤ n)
    (hs : s ∈ 𝓝 x) : HasStrictFderivAt f ((continuousMultilinearCurryFin1 𝕂 E' F') (p x 1)) x :=
  hasStrictFderivAt_of_hasFderivAt_of_continuousAt (hf.eventually_hasFderivAt hn hs) <|
    (continuousMultilinearCurryFin1 𝕂 E' F').ContinuousAt.comp <| (hf.cont 1 hn).ContinuousAt hs
#align has_ftaylor_series_up_to_on.has_strict_fderiv_at HasFtaylorSeriesUpToOn.hasStrictFderivAt

/-- If a function is `C^n` with `1 ≤ n` around a point, and its derivative at that point is given to
us as `f'`, then `f'` is also a strict derivative. -/
theorem ContDiffAt.has_strict_fderiv_at' {f : E' → F'} {f' : E' →L[𝕂] F'} {x : E'}
    (hf : ContDiffAt 𝕂 n f x) (hf' : HasFderivAt f f' x) (hn : 1 ≤ n) : HasStrictFderivAt f f' x :=
  by
  rcases hf 1 hn with ⟨u, H, p, hp⟩
  simp only [nhdsWithin_univ, mem_univ, insert_eq_of_mem] at H
  have := hp.has_strict_fderiv_at le_rfl H
  rwa [hf'.unique this.has_fderiv_at]
#align cont_diff_at.has_strict_fderiv_at' ContDiffAt.has_strict_fderiv_at'

/-- If a function is `C^n` with `1 ≤ n` around a point, and its derivative at that point is given to
us as `f'`, then `f'` is also a strict derivative. -/
theorem ContDiffAt.has_strict_deriv_at' {f : 𝕂 → F'} {f' : F'} {x : 𝕂} (hf : ContDiffAt 𝕂 n f x)
    (hf' : HasDerivAt f f' x) (hn : 1 ≤ n) : HasStrictDerivAt f f' x :=
  hf.has_strict_fderiv_at' hf' hn
#align cont_diff_at.has_strict_deriv_at' ContDiffAt.has_strict_deriv_at'

/-- If a function is `C^n` with `1 ≤ n` around a point, then the derivative of `f` at this point
is also a strict derivative. -/
theorem ContDiffAt.hasStrictFderivAt {f : E' → F'} {x : E'} (hf : ContDiffAt 𝕂 n f x) (hn : 1 ≤ n) :
    HasStrictFderivAt f (fderiv 𝕂 f x) x :=
  hf.has_strict_fderiv_at' (hf.DifferentiableAt hn).HasFderivAt hn
#align cont_diff_at.has_strict_fderiv_at ContDiffAt.hasStrictFderivAt

/-- If a function is `C^n` with `1 ≤ n` around a point, then the derivative of `f` at this point
is also a strict derivative. -/
theorem ContDiffAt.hasStrictDerivAt {f : 𝕂 → F'} {x : 𝕂} (hf : ContDiffAt 𝕂 n f x) (hn : 1 ≤ n) :
    HasStrictDerivAt f (deriv f x) x :=
  (hf.HasStrictFderivAt hn).HasStrictDerivAt
#align cont_diff_at.has_strict_deriv_at ContDiffAt.hasStrictDerivAt

/-- If a function is `C^n` with `1 ≤ n`, then the derivative of `f` is also a strict derivative. -/
theorem ContDiff.hasStrictFderivAt {f : E' → F'} {x : E'} (hf : ContDiff 𝕂 n f) (hn : 1 ≤ n) :
    HasStrictFderivAt f (fderiv 𝕂 f x) x :=
  hf.ContDiffAt.HasStrictFderivAt hn
#align cont_diff.has_strict_fderiv_at ContDiff.hasStrictFderivAt

/-- If a function is `C^n` with `1 ≤ n`, then the derivative of `f` is also a strict derivative. -/
theorem ContDiff.hasStrictDerivAt {f : 𝕂 → F'} {x : 𝕂} (hf : ContDiff 𝕂 n f) (hn : 1 ≤ n) :
    HasStrictDerivAt f (deriv f x) x :=
  hf.ContDiffAt.HasStrictDerivAt hn
#align cont_diff.has_strict_deriv_at ContDiff.hasStrictDerivAt

/-- If `f` has a formal Taylor series `p` up to order `1` on `{x} ∪ s`, where `s` is a convex set,
and `‖p x 1‖₊ < K`, then `f` is `K`-Lipschitz in a neighborhood of `x` within `s`. -/
theorem HasFtaylorSeriesUpToOn.exists_lipschitzOnWith_of_nnnorm_lt {E F : Type _}
    [NormedAddCommGroup E] [NormedSpace ℝ E] [NormedAddCommGroup F] [NormedSpace ℝ F] {f : E → F}
    {p : E → FormalMultilinearSeries ℝ E F} {s : Set E} {x : E}
    (hf : HasFtaylorSeriesUpToOn 1 f p (insert x s)) (hs : Convex ℝ s) (K : ℝ≥0)
    (hK : ‖p x 1‖₊ < K) : ∃ t ∈ 𝓝[s] x, LipschitzOnWith K f t :=
  by
  set f' := fun y => continuousMultilinearCurryFin1 ℝ E F (p y 1)
  have hder : ∀ y ∈ s, HasFderivWithinAt f (f' y) s y := fun y hy =>
    (hf.has_fderiv_within_at le_rfl (subset_insert x s hy)).mono (subset_insert x s)
  have hcont : ContinuousWithinAt f' s x :=
    (continuousMultilinearCurryFin1 ℝ E F).ContinuousAt.comp_continuousWithinAt
      ((hf.cont _ le_rfl _ (mem_insert _ _)).mono (subset_insert x s))
  replace hK : ‖f' x‖₊ < K
  · simpa only [LinearIsometryEquiv.nnnorm_map]
  exact
    hs.exists_nhds_within_lipschitz_on_with_of_has_fderiv_within_at_of_nnnorm_lt
      (eventually_nhdsWithin_iff.2 <| eventually_of_forall hder) hcont K hK
#align has_ftaylor_series_up_to_on.exists_lipschitz_on_with_of_nnnorm_lt HasFtaylorSeriesUpToOn.exists_lipschitzOnWith_of_nnnorm_lt

/-- If `f` has a formal Taylor series `p` up to order `1` on `{x} ∪ s`, where `s` is a convex set,
then `f` is Lipschitz in a neighborhood of `x` within `s`. -/
theorem HasFtaylorSeriesUpToOn.exists_lipschitzOnWith {E F : Type _} [NormedAddCommGroup E]
    [NormedSpace ℝ E] [NormedAddCommGroup F] [NormedSpace ℝ F] {f : E → F}
    {p : E → FormalMultilinearSeries ℝ E F} {s : Set E} {x : E}
    (hf : HasFtaylorSeriesUpToOn 1 f p (insert x s)) (hs : Convex ℝ s) :
    ∃ K, ∃ t ∈ 𝓝[s] x, LipschitzOnWith K f t :=
  (exists_gt _).imp <| hf.exists_lipschitzOnWith_of_nnnorm_lt hs
#align has_ftaylor_series_up_to_on.exists_lipschitz_on_with HasFtaylorSeriesUpToOn.exists_lipschitzOnWith

/-- If `f` is `C^1` within a conves set `s` at `x`, then it is Lipschitz on a neighborhood of `x`
within `s`. -/
theorem ContDiffWithinAt.exists_lipschitzOnWith {E F : Type _} [NormedAddCommGroup E]
    [NormedSpace ℝ E] [NormedAddCommGroup F] [NormedSpace ℝ F] {f : E → F} {s : Set E} {x : E}
    (hf : ContDiffWithinAt ℝ 1 f s x) (hs : Convex ℝ s) :
    ∃ K : ℝ≥0, ∃ t ∈ 𝓝[s] x, LipschitzOnWith K f t :=
  by
  rcases hf 1 le_rfl with ⟨t, hst, p, hp⟩
  rcases metric.mem_nhds_within_iff.mp hst with ⟨ε, ε0, hε⟩
  replace hp : HasFtaylorSeriesUpToOn 1 f p (Metric.ball x ε ∩ insert x s) := hp.mono hε
  clear hst hε t
  rw [← insert_eq_of_mem (Metric.mem_ball_self ε0), ← insert_inter_distrib] at hp
  rcases hp.exists_lipschitz_on_with ((convex_ball _ _).inter hs) with ⟨K, t, hst, hft⟩
  rw [inter_comm, ← nhdsWithin_restrict' _ (Metric.ball_mem_nhds _ ε0)] at hst
  exact ⟨K, t, hst, hft⟩
#align cont_diff_within_at.exists_lipschitz_on_with ContDiffWithinAt.exists_lipschitzOnWith

/-- If `f` is `C^1` at `x` and `K > ‖fderiv 𝕂 f x‖`, then `f` is `K`-Lipschitz in a neighborhood of
`x`. -/
theorem ContDiffAt.exists_lipschitzOnWith_of_nnnorm_lt {f : E' → F'} {x : E'}
    (hf : ContDiffAt 𝕂 1 f x) (K : ℝ≥0) (hK : ‖fderiv 𝕂 f x‖₊ < K) :
    ∃ t ∈ 𝓝 x, LipschitzOnWith K f t :=
  (hf.HasStrictFderivAt le_rfl).exists_lipschitzOnWith_of_nnnorm_lt K hK
#align cont_diff_at.exists_lipschitz_on_with_of_nnnorm_lt ContDiffAt.exists_lipschitzOnWith_of_nnnorm_lt

/-- If `f` is `C^1` at `x`, then `f` is Lipschitz in a neighborhood of `x`. -/
theorem ContDiffAt.exists_lipschitzOnWith {f : E' → F'} {x : E'} (hf : ContDiffAt 𝕂 1 f x) :
    ∃ K, ∃ t ∈ 𝓝 x, LipschitzOnWith K f t :=
  (hf.HasStrictFderivAt le_rfl).exists_lipschitzOnWith
#align cont_diff_at.exists_lipschitz_on_with ContDiffAt.exists_lipschitzOnWith

end Real

section deriv

/-!
### One dimension

All results up to now have been expressed in terms of the general Fréchet derivative `fderiv`. For
maps defined on the field, the one-dimensional derivative `deriv` is often easier to use. In this
paragraph, we reformulate some higher smoothness results in terms of `deriv`.
-/

variable {f₂ : 𝕜 → F} {s₂ : Set 𝕜}

open ContinuousLinearMap (smul_right)

/-- A function is `C^(n + 1)` on a domain with unique derivatives if and only if it is
differentiable there, and its derivative (formulated with `deriv_within`) is `C^n`. -/
theorem contDiffOn_succ_iff_derivWithin {n : ℕ} (hs : UniqueDiffOn 𝕜 s₂) :
    ContDiffOn 𝕜 (n + 1 : ℕ) f₂ s₂ ↔
      DifferentiableOn 𝕜 f₂ s₂ ∧ ContDiffOn 𝕜 n (derivWithin f₂ s₂) s₂ :=
  by
  rw [contDiffOn_succ_iff_fderivWithin hs]
  congr 2
  apply le_antisymm
  · intro h
    have : derivWithin f₂ s₂ = (fun u : 𝕜 →L[𝕜] F => u 1) ∘ fderivWithin 𝕜 f₂ s₂ :=
      by
      ext x
      rfl
    simp only [this]
    apply ContDiff.comp_contDiffOn _ h
    exact (is_bounded_bilinear_map_apply.is_bounded_linear_map_left _).ContDiff
  · intro h
    have : fderivWithin 𝕜 f₂ s₂ = smul_right (1 : 𝕜 →L[𝕜] 𝕜) ∘ derivWithin f₂ s₂ :=
      by
      ext x
      simp [derivWithin]
    simp only [this]
    apply ContDiff.comp_contDiffOn _ h
    have : IsBoundedBilinearMap 𝕜 fun _ : (𝕜 →L[𝕜] 𝕜) × F => _ := isBoundedBilinearMapSmulRight
    exact (this.is_bounded_linear_map_right _).ContDiff
#align cont_diff_on_succ_iff_deriv_within contDiffOn_succ_iff_derivWithin

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:73:14: unsupported tactic `congrm #[[expr «expr ∧ »(_, _)]] -/
/-- A function is `C^(n + 1)` on an open domain if and only if it is
differentiable there, and its derivative (formulated with `deriv`) is `C^n`. -/
theorem contDiffOn_succ_iff_deriv_of_open {n : ℕ} (hs : IsOpen s₂) :
    ContDiffOn 𝕜 (n + 1 : ℕ) f₂ s₂ ↔ DifferentiableOn 𝕜 f₂ s₂ ∧ ContDiffOn 𝕜 n (deriv f₂) s₂ :=
  by
  rw [contDiffOn_succ_iff_derivWithin hs.unique_diff_on]
  trace
    "./././Mathport/Syntax/Translate/Tactic/Builtin.lean:73:14: unsupported tactic `congrm #[[expr «expr ∧ »(_, _)]]"
  exact contDiffOn_congr fun _ => derivWithin_of_open hs
#align cont_diff_on_succ_iff_deriv_of_open contDiffOn_succ_iff_deriv_of_open

/-- A function is `C^∞` on a domain with unique derivatives if and only if it is differentiable
there, and its derivative (formulated with `deriv_within`) is `C^∞`. -/
theorem contDiffOn_top_iff_derivWithin (hs : UniqueDiffOn 𝕜 s₂) :
    ContDiffOn 𝕜 ∞ f₂ s₂ ↔ DifferentiableOn 𝕜 f₂ s₂ ∧ ContDiffOn 𝕜 ∞ (derivWithin f₂ s₂) s₂ :=
  by
  constructor
  · intro h
    refine' ⟨h.differentiable_on le_top, _⟩
    apply contDiffOn_top.2 fun n => ((contDiffOn_succ_iff_derivWithin hs).1 _).2
    exact h.of_le le_top
  · intro h
    refine' contDiffOn_top.2 fun n => _
    have A : (n : ℕ∞) ≤ ∞ := le_top
    apply ((contDiffOn_succ_iff_derivWithin hs).2 ⟨h.1, h.2.of_le A⟩).of_le
    exact WithTop.coe_le_coe.2 (Nat.le_succ n)
#align cont_diff_on_top_iff_deriv_within contDiffOn_top_iff_derivWithin

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:73:14: unsupported tactic `congrm #[[expr «expr ∧ »(_, _)]] -/
/-- A function is `C^∞` on an open domain if and only if it is differentiable
there, and its derivative (formulated with `deriv`) is `C^∞`. -/
theorem contDiffOn_top_iff_deriv_of_open (hs : IsOpen s₂) :
    ContDiffOn 𝕜 ∞ f₂ s₂ ↔ DifferentiableOn 𝕜 f₂ s₂ ∧ ContDiffOn 𝕜 ∞ (deriv f₂) s₂ :=
  by
  rw [contDiffOn_top_iff_derivWithin hs.unique_diff_on]
  trace
    "./././Mathport/Syntax/Translate/Tactic/Builtin.lean:73:14: unsupported tactic `congrm #[[expr «expr ∧ »(_, _)]]"
  exact contDiffOn_congr fun _ => derivWithin_of_open hs
#align cont_diff_on_top_iff_deriv_of_open contDiffOn_top_iff_deriv_of_open

theorem ContDiffOn.derivWithin (hf : ContDiffOn 𝕜 n f₂ s₂) (hs : UniqueDiffOn 𝕜 s₂)
    (hmn : m + 1 ≤ n) : ContDiffOn 𝕜 m (derivWithin f₂ s₂) s₂ :=
  by
  cases m
  · change ∞ + 1 ≤ n at hmn
    have : n = ∞ := by simpa using hmn
    rw [this] at hf
    exact ((contDiffOn_top_iff_derivWithin hs).1 hf).2
  · change (m.succ : ℕ∞) ≤ n at hmn
    exact ((contDiffOn_succ_iff_derivWithin hs).1 (hf.of_le hmn)).2
#align cont_diff_on.deriv_within ContDiffOn.derivWithin

theorem ContDiffOn.deriv_of_open (hf : ContDiffOn 𝕜 n f₂ s₂) (hs : IsOpen s₂) (hmn : m + 1 ≤ n) :
    ContDiffOn 𝕜 m (deriv f₂) s₂ :=
  (hf.derivWithin hs.UniqueDiffOn hmn).congr fun x hx => (derivWithin_of_open hs hx).symm
#align cont_diff_on.deriv_of_open ContDiffOn.deriv_of_open

theorem ContDiffOn.continuousOn_derivWithin (h : ContDiffOn 𝕜 n f₂ s₂) (hs : UniqueDiffOn 𝕜 s₂)
    (hn : 1 ≤ n) : ContinuousOn (derivWithin f₂ s₂) s₂ :=
  ((contDiffOn_succ_iff_derivWithin hs).1 (h.of_le hn)).2.ContinuousOn
#align cont_diff_on.continuous_on_deriv_within ContDiffOn.continuousOn_derivWithin

theorem ContDiffOn.continuousOn_deriv_of_open (h : ContDiffOn 𝕜 n f₂ s₂) (hs : IsOpen s₂)
    (hn : 1 ≤ n) : ContinuousOn (deriv f₂) s₂ :=
  ((contDiffOn_succ_iff_deriv_of_open hs).1 (h.of_le hn)).2.ContinuousOn
#align cont_diff_on.continuous_on_deriv_of_open ContDiffOn.continuousOn_deriv_of_open

/-- A function is `C^(n + 1)` if and only if it is differentiable,
  and its derivative (formulated in terms of `deriv`) is `C^n`. -/
theorem contDiff_succ_iff_deriv {n : ℕ} :
    ContDiff 𝕜 (n + 1 : ℕ) f₂ ↔ Differentiable 𝕜 f₂ ∧ ContDiff 𝕜 n (deriv f₂) := by
  simp only [← contDiffOn_univ, contDiffOn_succ_iff_deriv_of_open, isOpen_univ,
    differentiableOn_univ]
#align cont_diff_succ_iff_deriv contDiff_succ_iff_deriv

theorem contDiff_one_iff_deriv : ContDiff 𝕜 1 f₂ ↔ Differentiable 𝕜 f₂ ∧ Continuous (deriv f₂) :=
  contDiff_succ_iff_deriv.trans <| Iff.rfl.And contDiff_zero
#align cont_diff_one_iff_deriv contDiff_one_iff_deriv

/-- A function is `C^∞` if and only if it is differentiable,
and its derivative (formulated in terms of `deriv`) is `C^∞`. -/
theorem contDiff_top_iff_deriv : ContDiff 𝕜 ∞ f₂ ↔ Differentiable 𝕜 f₂ ∧ ContDiff 𝕜 ∞ (deriv f₂) :=
  by
  simp only [← contDiffOn_univ, ← differentiableOn_univ, ← derivWithin_univ]
  rw [contDiffOn_top_iff_derivWithin uniqueDiffOn_univ]
#align cont_diff_top_iff_deriv contDiff_top_iff_deriv

theorem ContDiff.continuous_deriv (h : ContDiff 𝕜 n f₂) (hn : 1 ≤ n) : Continuous (deriv f₂) :=
  (contDiff_succ_iff_deriv.mp (h.of_le hn)).2.Continuous
#align cont_diff.continuous_deriv ContDiff.continuous_deriv

end deriv

section RestrictScalars

/-!
### Restricting from `ℂ` to `ℝ`, or generally from `𝕜'` to `𝕜`

If a function is `n` times continuously differentiable over `ℂ`, then it is `n` times continuously
differentiable over `ℝ`. In this paragraph, we give variants of this statement, in the general
situation where `ℂ` and `ℝ` are replaced respectively by `𝕜'` and `𝕜` where `𝕜'` is a normed algebra
over `𝕜`.
-/

variable (𝕜) {𝕜' : Type _} [NontriviallyNormedField 𝕜'] [NormedAlgebra 𝕜 𝕜']

variable [NormedSpace 𝕜' E] [IsScalarTower 𝕜 𝕜' E]

variable [NormedSpace 𝕜' F] [IsScalarTower 𝕜 𝕜' F]

variable {p' : E → FormalMultilinearSeries 𝕜' E F}

theorem HasFtaylorSeriesUpToOn.restrictScalars (h : HasFtaylorSeriesUpToOn n f p' s) :
    HasFtaylorSeriesUpToOn n f (fun x => (p' x).restrictScalars 𝕜) s :=
  { zero_eq := fun x hx => h.zero_eq x hx
    fderivWithin := by
      intro m hm x hx
      convert(ContinuousMultilinearMap.restrictScalarsLinear 𝕜).HasFderivAt.comp_hasFderivWithinAt _
          ((h.fderiv_within m hm x hx).restrictScalars 𝕜)
    cont := fun m hm =>
      ContinuousMultilinearMap.continuous_restrictScalars.comp_continuousOn (h.cont m hm) }
#align has_ftaylor_series_up_to_on.restrict_scalars HasFtaylorSeriesUpToOn.restrictScalars

theorem ContDiffWithinAt.restrict_scalars (h : ContDiffWithinAt 𝕜' n f s x) :
    ContDiffWithinAt 𝕜 n f s x := by
  intro m hm
  rcases h m hm with ⟨u, u_mem, p', hp'⟩
  exact ⟨u, u_mem, _, hp'.restrict_scalars _⟩
#align cont_diff_within_at.restrict_scalars ContDiffWithinAt.restrict_scalars

theorem ContDiffOn.restrict_scalars (h : ContDiffOn 𝕜' n f s) : ContDiffOn 𝕜 n f s := fun x hx =>
  (h x hx).restrictScalars _
#align cont_diff_on.restrict_scalars ContDiffOn.restrict_scalars

theorem ContDiffAt.restrict_scalars (h : ContDiffAt 𝕜' n f x) : ContDiffAt 𝕜 n f x :=
  contDiffWithinAt_univ.1 <| h.ContDiffWithinAt.restrictScalars _
#align cont_diff_at.restrict_scalars ContDiffAt.restrict_scalars

theorem ContDiff.restrict_scalars (h : ContDiff 𝕜' n f) : ContDiff 𝕜 n f :=
  contDiff_iff_contDiffAt.2 fun x => h.ContDiffAt.restrictScalars _
#align cont_diff.restrict_scalars ContDiff.restrict_scalars

end RestrictScalars

/-!## Quantitative bounds -/

/-- Bounding the norm of the iterated derivative of `B (f x) (g x)` within a set in terms of the
iterated derivatives of `f` and `g` when `B` is bilinear. This lemma is an auxiliary version
assuming all spaces live in the same universe, to enable an induction. Use instead
`continuous_linear_map.norm_iterated_fderiv_within_le_of_bilinear` that removes this assumption. -/
theorem ContinuousLinearMap.norm_iteratedFderivWithin_le_of_bilinear_aux {Du Eu Fu Gu : Type u}
    [NormedAddCommGroup Du] [NormedSpace 𝕜 Du] [NormedAddCommGroup Eu] [NormedSpace 𝕜 Eu]
    [NormedAddCommGroup Fu] [NormedSpace 𝕜 Fu] [NormedAddCommGroup Gu] [NormedSpace 𝕜 Gu]
    (B : Eu →L[𝕜] Fu →L[𝕜] Gu) {f : Du → Eu} {g : Du → Fu} {n : ℕ} {s : Set Du} {x : Du}
    (hf : ContDiffOn 𝕜 n f s) (hg : ContDiffOn 𝕜 n g s) (hs : UniqueDiffOn 𝕜 s) (hx : x ∈ s) :
    ‖iteratedFderivWithin 𝕜 n (fun y => B (f y) (g y)) s x‖ ≤
      ‖B‖ *
        ∑ i in Finset.range (n + 1),
          (n.choose i : ℝ) * ‖iteratedFderivWithin 𝕜 i f s x‖ *
            ‖iteratedFderivWithin 𝕜 (n - i) g s x‖ :=
  by
  /- We argue by induction on `n`. The bound is trivial for `n = 0`. For `n + 1`, we write
    the `(n+1)`-th derivative as the `n`-th derivative of the derivative `B f g' + B f' g`, and apply
    the inductive assumption to each of those two terms. For this induction to make sense,
    the spaces of linear maps that appear in the induction should be in the same universe as the
    original spaces, which explains why we assume in the lemma that all spaces live in the same
    universe. -/
  induction' n with n IH generalizing Eu Fu Gu
  · simp only [← mul_assoc, norm_iteratedFderivWithin_zero, Finset.range_one, Finset.sum_singleton,
      Nat.choose_self, algebraMap.coe_one, one_mul]
    apply ((B (f x)).le_op_norm (g x)).trans
    apply mul_le_mul_of_nonneg_right _ (norm_nonneg _)
    exact B.le_op_norm (f x)
  · have In : (n : WithTop ℕ) + 1 ≤ n.succ := by simp only [Nat.cast_succ, le_refl]
    have I1 :
      ‖iteratedFderivWithin 𝕜 n (fun y : Du => B.precompR Du (f y) (fderivWithin 𝕜 g s y)) s x‖ ≤
        ‖B‖ *
          ∑ i : ℕ in Finset.range (n + 1),
            n.choose i * ‖iteratedFderivWithin 𝕜 i f s x‖ *
              ‖iteratedFderivWithin 𝕜 (n + 1 - i) g s x‖ :=
      calc
        ‖iteratedFderivWithin 𝕜 n (fun y : Du => B.precompR Du (f y) (fderivWithin 𝕜 g s y)) s x‖ ≤
            ‖B.precompR Du‖ *
              ∑ i : ℕ in Finset.range (n + 1),
                n.choose i * ‖iteratedFderivWithin 𝕜 i f s x‖ *
                  ‖iteratedFderivWithin 𝕜 (n - i) (fderivWithin 𝕜 g s) s x‖ :=
          IH _ (hf.of_le (Nat.cast_le.2 (Nat.le_succ n))) (hg.fderiv_within hs In)
        _ ≤
            ‖B‖ *
              ∑ i : ℕ in Finset.range (n + 1),
                n.choose i * ‖iteratedFderivWithin 𝕜 i f s x‖ *
                  ‖iteratedFderivWithin 𝕜 (n - i) (fderivWithin 𝕜 g s) s x‖ :=
          (mul_le_mul_of_nonneg_right (B.norm_precompR_le Du)
            (Finset.sum_nonneg' fun i => by positivity))
        _ = _ := by
          congr 1
          apply Finset.sum_congr rfl fun i hi => _
          rw [Nat.succ_sub (Nat.lt_succ_iff.1 (Finset.mem_range.1 hi)),
            iteratedFderivWithin_succ_eq_comp_right hs hx, LinearIsometryEquiv.norm_map]

    have I2 :
      ‖iteratedFderivWithin 𝕜 n (fun y : Du => B.precompL Du (fderivWithin 𝕜 f s y) (g y)) s x‖ ≤
        ‖B‖ *
          ∑ i : ℕ in Finset.range (n + 1),
            n.choose i * ‖iteratedFderivWithin 𝕜 (i + 1) f s x‖ *
              ‖iteratedFderivWithin 𝕜 (n - i) g s x‖ :=
      calc
        ‖iteratedFderivWithin 𝕜 n (fun y : Du => B.precompL Du (fderivWithin 𝕜 f s y) (g y)) s x‖ ≤
            ‖B.precompL Du‖ *
              ∑ i : ℕ in Finset.range (n + 1),
                n.choose i * ‖iteratedFderivWithin 𝕜 i (fderivWithin 𝕜 f s) s x‖ *
                  ‖iteratedFderivWithin 𝕜 (n - i) g s x‖ :=
          IH _ (hf.fderiv_within hs In) (hg.of_le (Nat.cast_le.2 (Nat.le_succ n)))
        _ ≤
            ‖B‖ *
              ∑ i : ℕ in Finset.range (n + 1),
                n.choose i * ‖iteratedFderivWithin 𝕜 i (fderivWithin 𝕜 f s) s x‖ *
                  ‖iteratedFderivWithin 𝕜 (n - i) g s x‖ :=
          (mul_le_mul_of_nonneg_right (B.norm_precompL_le Du)
            (Finset.sum_nonneg' fun i => by positivity))
        _ = _ := by
          congr 1
          apply Finset.sum_congr rfl fun i hi => _
          rw [iteratedFderivWithin_succ_eq_comp_right hs hx, LinearIsometryEquiv.norm_map]

    have J :
      iteratedFderivWithin 𝕜 n (fun y : Du => fderivWithin 𝕜 (fun y : Du => B (f y) (g y)) s y) s
          x =
        iteratedFderivWithin 𝕜 n
          (fun y =>
            B.precompR Du (f y) (fderivWithin 𝕜 g s y) + B.precompL Du (fderivWithin 𝕜 f s y) (g y))
          s x :=
      by
      apply iteratedFderivWithin_congr hs (fun y hy => _) hx
      have L : (1 : WithTop ℕ) ≤ n.succ := by
        simpa only [ENat.coe_one, Nat.one_le_cast] using Nat.succ_pos n
      exact
        B.fderiv_within_of_bilinear (hf.differentiable_on L y hy) (hg.differentiable_on L y hy)
          (hs y hy)
    rw [iteratedFderivWithin_succ_eq_comp_right hs hx, LinearIsometryEquiv.norm_map, J]
    have A : ContDiffOn 𝕜 n (fun y => B.precompR Du (f y) (fderivWithin 𝕜 g s y)) s :=
      (B.precompR Du).IsBoundedBilinearMap.ContDiff.comp_contDiff_on₂
        (hf.of_le (Nat.cast_le.2 (Nat.le_succ n))) (hg.fderiv_within hs In)
    have A' : ContDiffOn 𝕜 n (fun y => B.precompL Du (fderivWithin 𝕜 f s y) (g y)) s :=
      (B.precompL Du).IsBoundedBilinearMap.ContDiff.comp_contDiff_on₂ (hf.fderiv_within hs In)
        (hg.of_le (Nat.cast_le.2 (Nat.le_succ n)))
    rw [iteratedFderivWithin_add_apply' A A' hs hx]
    apply (norm_add_le _ _).trans ((add_le_add I1 I2).trans (le_of_eq _))
    simp_rw [← mul_add, mul_assoc]
    congr 1
    exact
      (Finset.sum_choose_succ_mul
          (fun i j => ‖iteratedFderivWithin 𝕜 i f s x‖ * ‖iteratedFderivWithin 𝕜 j g s x‖) n).symm
#align continuous_linear_map.norm_iterated_fderiv_within_le_of_bilinear_aux ContinuousLinearMap.norm_iteratedFderivWithin_le_of_bilinear_aux

/-- Bounding the norm of the iterated derivative of `B (f x) (g x)` within a set in terms of the
iterated derivatives of `f` and `g` when `B` is bilinear:
`‖D^n (x ↦ B (f x) (g x))‖ ≤ ‖B‖ ∑_{k ≤ n} n.choose k ‖D^k f‖ ‖D^{n-k} g‖` -/
theorem ContinuousLinearMap.norm_iteratedFderivWithin_le_of_bilinear (B : E →L[𝕜] F →L[𝕜] G)
    {f : D → E} {g : D → F} {N : WithTop ℕ} {s : Set D} {x : D} (hf : ContDiffOn 𝕜 N f s)
    (hg : ContDiffOn 𝕜 N g s) (hs : UniqueDiffOn 𝕜 s) (hx : x ∈ s) {n : ℕ}
    (hn : (n : WithTop ℕ) ≤ N) :
    ‖iteratedFderivWithin 𝕜 n (fun y => B (f y) (g y)) s x‖ ≤
      ‖B‖ *
        ∑ i in Finset.range (n + 1),
          (n.choose i : ℝ) * ‖iteratedFderivWithin 𝕜 i f s x‖ *
            ‖iteratedFderivWithin 𝕜 (n - i) g s x‖ :=
  by
  /- We reduce the bound to the case where all spaces live in the same universe (in which we
    already have proved the result), by using linear isometries between the spaces and their `ulift`
    to a common universe. These linear isometries preserve the norm of the iterated derivative. -/
  let Du : Type max uD uE uF uG := ULift.{max uE uF uG, uD} D
  let Eu : Type max uD uE uF uG := ULift.{max uD uF uG, uE} E
  let Fu : Type max uD uE uF uG := ULift.{max uD uE uG, uF} F
  let Gu : Type max uD uE uF uG := ULift.{max uD uE uF, uG} G
  have isoD : Du ≃ₗᵢ[𝕜] D := LinearIsometryEquiv.ulift 𝕜 D
  have isoE : Eu ≃ₗᵢ[𝕜] E := LinearIsometryEquiv.ulift 𝕜 E
  have isoF : Fu ≃ₗᵢ[𝕜] F := LinearIsometryEquiv.ulift 𝕜 F
  have isoG : Gu ≃ₗᵢ[𝕜] G := LinearIsometryEquiv.ulift 𝕜 G
  -- lift `f` and `g` to versions `fu` and `gu` on the lifted spaces.
  let fu : Du → Eu := isoE.symm ∘ f ∘ isoD
  let gu : Du → Fu := isoF.symm ∘ g ∘ isoD
  -- lift the bilinear map `B` to a bilinear map `Bu` on the lifted spaces.
  let Bu₀ : Eu →L[𝕜] Fu →L[𝕜] G
  exact ((B.comp (isoE : Eu →L[𝕜] E)).flip.comp (isoF : Fu →L[𝕜] F)).flip
  let Bu : Eu →L[𝕜] Fu →L[𝕜] Gu
  exact
    ContinuousLinearMap.compL 𝕜 Eu (Fu →L[𝕜] G) (Fu →L[𝕜] Gu)
      (ContinuousLinearMap.compL 𝕜 Fu G Gu (isoG.symm : G →L[𝕜] Gu)) Bu₀
  have Bu_eq : (fun y => Bu (fu y) (gu y)) = isoG.symm ∘ (fun y => B (f y) (g y)) ∘ isoD :=
    by
    ext1 y
    simp only [Bu, ContinuousLinearMap.compL_apply, Function.comp_apply,
      ContinuousLinearMap.coe_comp', LinearIsometryEquiv.coe_coe'', ContinuousLinearMap.flip_apply,
      LinearIsometryEquiv.apply_symm_apply]
  -- All norms are preserved by the lifting process.
  have Bu_le : ‖Bu‖ ≤ ‖B‖ :=
    by
    refine' ContinuousLinearMap.op_norm_le_bound _ (norm_nonneg _) fun y => _
    refine' ContinuousLinearMap.op_norm_le_bound _ (by positivity) fun x => _
    simp only [Bu, ContinuousLinearMap.compL_apply, ContinuousLinearMap.coe_comp',
      Function.comp_apply, LinearIsometryEquiv.coe_coe'', ContinuousLinearMap.flip_apply,
      LinearIsometryEquiv.norm_map]
    calc
      ‖B (isoE y) (isoF x)‖ ≤ ‖B (isoE y)‖ * ‖isoF x‖ := ContinuousLinearMap.le_op_norm _ _
      _ ≤ ‖B‖ * ‖isoE y‖ * ‖isoF x‖ :=
        (mul_le_mul_of_nonneg_right (ContinuousLinearMap.le_op_norm _ _) (norm_nonneg _))
      _ = ‖B‖ * ‖y‖ * ‖x‖ := by simp only [LinearIsometryEquiv.norm_map]

  let su := isoD ⁻¹' s
  have hsu : UniqueDiffOn 𝕜 su := isoD.to_continuous_linear_equiv.unique_diff_on_preimage_iff.2 hs
  let xu := isoD.symm x
  have hxu : xu ∈ su := by
    simpa only [Set.mem_preimage, LinearIsometryEquiv.apply_symm_apply] using hx
  have xu_x : isoD xu = x := by simp only [LinearIsometryEquiv.apply_symm_apply]
  have hfu : ContDiffOn 𝕜 n fu su :=
    isoE.symm.cont_diff.comp_cont_diff_on
      ((hf.of_le hn).compContinuousLinearMap (isoD : Du →L[𝕜] D))
  have hgu : ContDiffOn 𝕜 n gu su :=
    isoF.symm.cont_diff.comp_cont_diff_on
      ((hg.of_le hn).compContinuousLinearMap (isoD : Du →L[𝕜] D))
  have Nfu : ∀ i, ‖iteratedFderivWithin 𝕜 i fu su xu‖ = ‖iteratedFderivWithin 𝕜 i f s x‖ :=
    by
    intro i
    rw [LinearIsometryEquiv.norm_iteratedFderivWithin_comp_left _ _ hsu hxu]
    rw [LinearIsometryEquiv.norm_iteratedFderivWithin_comp_right _ _ hs, xu_x]
    rwa [← xu_x] at hx
  have Ngu : ∀ i, ‖iteratedFderivWithin 𝕜 i gu su xu‖ = ‖iteratedFderivWithin 𝕜 i g s x‖ :=
    by
    intro i
    rw [LinearIsometryEquiv.norm_iteratedFderivWithin_comp_left _ _ hsu hxu]
    rw [LinearIsometryEquiv.norm_iteratedFderivWithin_comp_right _ _ hs, xu_x]
    rwa [← xu_x] at hx
  have NBu :
    ‖iteratedFderivWithin 𝕜 n (fun y => Bu (fu y) (gu y)) su xu‖ =
      ‖iteratedFderivWithin 𝕜 n (fun y => B (f y) (g y)) s x‖ :=
    by
    rw [Bu_eq]
    rw [LinearIsometryEquiv.norm_iteratedFderivWithin_comp_left _ _ hsu hxu]
    rw [LinearIsometryEquiv.norm_iteratedFderivWithin_comp_right _ _ hs, xu_x]
    rwa [← xu_x] at hx
  -- state the bound for the lifted objects, and deduce the original bound from it.
  have :
    ‖iteratedFderivWithin 𝕜 n (fun y => Bu (fu y) (gu y)) su xu‖ ≤
      ‖Bu‖ *
        ∑ i in Finset.range (n + 1),
          (n.choose i : ℝ) * ‖iteratedFderivWithin 𝕜 i fu su xu‖ *
            ‖iteratedFderivWithin 𝕜 (n - i) gu su xu‖ :=
    Bu.norm_iterated_fderiv_within_le_of_bilinear_aux hfu hgu hsu hxu
  simp only [Nfu, Ngu, NBu] at this
  apply this.trans (mul_le_mul_of_nonneg_right Bu_le _)
  exact Finset.sum_nonneg' fun i => by positivity
#align continuous_linear_map.norm_iterated_fderiv_within_le_of_bilinear ContinuousLinearMap.norm_iteratedFderivWithin_le_of_bilinear

/-- Bounding the norm of the iterated derivative of `B (f x) (g x)` in terms of the
iterated derivatives of `f` and `g` when `B` is bilinear:
`‖D^n (x ↦ B (f x) (g x))‖ ≤ ‖B‖ ∑_{k ≤ n} n.choose k ‖D^k f‖ ‖D^{n-k} g‖` -/
theorem ContinuousLinearMap.norm_iteratedFderiv_le_of_bilinear (B : E →L[𝕜] F →L[𝕜] G) {f : D → E}
    {g : D → F} {N : WithTop ℕ} (hf : ContDiff 𝕜 N f) (hg : ContDiff 𝕜 N g) (x : D) {n : ℕ}
    (hn : (n : WithTop ℕ) ≤ N) :
    ‖iteratedFderiv 𝕜 n (fun y => B (f y) (g y)) x‖ ≤
      ‖B‖ *
        ∑ i in Finset.range (n + 1),
          (n.choose i : ℝ) * ‖iteratedFderiv 𝕜 i f x‖ * ‖iteratedFderiv 𝕜 (n - i) g x‖ :=
  by
  simp_rw [← iteratedFderivWithin_univ]
  exact
    B.norm_iterated_fderiv_within_le_of_bilinear hf.cont_diff_on hg.cont_diff_on uniqueDiffOn_univ
      (mem_univ x) hn
#align continuous_linear_map.norm_iterated_fderiv_le_of_bilinear ContinuousLinearMap.norm_iteratedFderiv_le_of_bilinear

/-- Bounding the norm of the iterated derivative of `B (f x) (g x)` within a set in terms of the
iterated derivatives of `f` and `g` when `B` is bilinear of norm at most `1`:
`‖D^n (x ↦ B (f x) (g x))‖ ≤ ∑_{k ≤ n} n.choose k ‖D^k f‖ ‖D^{n-k} g‖` -/
theorem ContinuousLinearMap.norm_iteratedFderivWithin_le_of_bilinear_of_le_one
    (B : E →L[𝕜] F →L[𝕜] G) {f : D → E} {g : D → F} {N : WithTop ℕ} {s : Set D} {x : D}
    (hf : ContDiffOn 𝕜 N f s) (hg : ContDiffOn 𝕜 N g s) (hs : UniqueDiffOn 𝕜 s) (hx : x ∈ s) {n : ℕ}
    (hn : (n : WithTop ℕ) ≤ N) (hB : ‖B‖ ≤ 1) :
    ‖iteratedFderivWithin 𝕜 n (fun y => B (f y) (g y)) s x‖ ≤
      ∑ i in Finset.range (n + 1),
        (n.choose i : ℝ) * ‖iteratedFderivWithin 𝕜 i f s x‖ *
          ‖iteratedFderivWithin 𝕜 (n - i) g s x‖ :=
  by
  apply (B.norm_iterated_fderiv_within_le_of_bilinear hf hg hs hx hn).trans
  apply mul_le_of_le_one_left (Finset.sum_nonneg' fun i => _) hB
  positivity
#align continuous_linear_map.norm_iterated_fderiv_within_le_of_bilinear_of_le_one ContinuousLinearMap.norm_iteratedFderivWithin_le_of_bilinear_of_le_one

/-- Bounding the norm of the iterated derivative of `B (f x) (g x)` in terms of the
iterated derivatives of `f` and `g` when `B` is bilinear of norm at most `1`:
`‖D^n (x ↦ B (f x) (g x))‖ ≤ ∑_{k ≤ n} n.choose k ‖D^k f‖ ‖D^{n-k} g‖` -/
theorem ContinuousLinearMap.norm_iteratedFderiv_le_of_bilinear_of_le_one (B : E →L[𝕜] F →L[𝕜] G)
    {f : D → E} {g : D → F} {N : WithTop ℕ} (hf : ContDiff 𝕜 N f) (hg : ContDiff 𝕜 N g) (x : D)
    {n : ℕ} (hn : (n : WithTop ℕ) ≤ N) (hB : ‖B‖ ≤ 1) :
    ‖iteratedFderiv 𝕜 n (fun y => B (f y) (g y)) x‖ ≤
      ∑ i in Finset.range (n + 1),
        (n.choose i : ℝ) * ‖iteratedFderiv 𝕜 i f x‖ * ‖iteratedFderiv 𝕜 (n - i) g x‖ :=
  by
  simp_rw [← iteratedFderivWithin_univ]
  exact
    B.norm_iterated_fderiv_within_le_of_bilinear_of_le_one hf.cont_diff_on hg.cont_diff_on
      uniqueDiffOn_univ (mem_univ x) hn hB
#align continuous_linear_map.norm_iterated_fderiv_le_of_bilinear_of_le_one ContinuousLinearMap.norm_iteratedFderiv_le_of_bilinear_of_le_one

section

variable {𝕜' : Type _} [NormedField 𝕜'] [NormedAlgebra 𝕜 𝕜'] [NormedSpace 𝕜' F]
  [IsScalarTower 𝕜 𝕜' F]

theorem norm_iteratedFderivWithin_smul_le {f : E → 𝕜'} {g : E → F} {N : WithTop ℕ}
    (hf : ContDiffOn 𝕜 N f s) (hg : ContDiffOn 𝕜 N g s) (hs : UniqueDiffOn 𝕜 s) {x : E} (hx : x ∈ s)
    {n : ℕ} (hn : (n : WithTop ℕ) ≤ N) :
    ‖iteratedFderivWithin 𝕜 n (fun y => f y • g y) s x‖ ≤
      ∑ i in Finset.range (n + 1),
        (n.choose i : ℝ) * ‖iteratedFderivWithin 𝕜 i f s x‖ *
          ‖iteratedFderivWithin 𝕜 (n - i) g s x‖ :=
  (ContinuousLinearMap.lsmul 𝕜 𝕜' :
        𝕜' →L[𝕜] F →L[𝕜] F).norm_iteratedFderivWithin_le_of_bilinear_of_le_one
    hf hg hs hx hn ContinuousLinearMap.op_norm_lsmul_le
#align norm_iterated_fderiv_within_smul_le norm_iteratedFderivWithin_smul_le

theorem norm_iteratedFderiv_smul_le {f : E → 𝕜'} {g : E → F} {N : WithTop ℕ} (hf : ContDiff 𝕜 N f)
    (hg : ContDiff 𝕜 N g) (x : E) {n : ℕ} (hn : (n : WithTop ℕ) ≤ N) :
    ‖iteratedFderiv 𝕜 n (fun y => f y • g y) x‖ ≤
      ∑ i in Finset.range (n + 1),
        (n.choose i : ℝ) * ‖iteratedFderiv 𝕜 i f x‖ * ‖iteratedFderiv 𝕜 (n - i) g x‖ :=
  (ContinuousLinearMap.lsmul 𝕜 𝕜' : 𝕜' →L[𝕜] F →L[𝕜] F).norm_iteratedFderiv_le_of_bilinear_of_le_one
    hf hg x hn ContinuousLinearMap.op_norm_lsmul_le
#align norm_iterated_fderiv_smul_le norm_iteratedFderiv_smul_le

end

section

variable {A : Type _} [NormedRing A] [NormedAlgebra 𝕜 A]

theorem norm_iteratedFderivWithin_mul_le {f : E → A} {g : E → A} {N : WithTop ℕ}
    (hf : ContDiffOn 𝕜 N f s) (hg : ContDiffOn 𝕜 N g s) (hs : UniqueDiffOn 𝕜 s) {x : E} (hx : x ∈ s)
    {n : ℕ} (hn : (n : WithTop ℕ) ≤ N) :
    ‖iteratedFderivWithin 𝕜 n (fun y => f y * g y) s x‖ ≤
      ∑ i in Finset.range (n + 1),
        (n.choose i : ℝ) * ‖iteratedFderivWithin 𝕜 i f s x‖ *
          ‖iteratedFderivWithin 𝕜 (n - i) g s x‖ :=
  (ContinuousLinearMap.mul 𝕜 A :
        A →L[𝕜] A →L[𝕜] A).norm_iteratedFderivWithin_le_of_bilinear_of_le_one
    hf hg hs hx hn (ContinuousLinearMap.op_norm_mul_le _ _)
#align norm_iterated_fderiv_within_mul_le norm_iteratedFderivWithin_mul_le

theorem norm_iteratedFderiv_mul_le {f : E → A} {g : E → A} {N : WithTop ℕ} (hf : ContDiff 𝕜 N f)
    (hg : ContDiff 𝕜 N g) (x : E) {n : ℕ} (hn : (n : WithTop ℕ) ≤ N) :
    ‖iteratedFderiv 𝕜 n (fun y => f y * g y) x‖ ≤
      ∑ i in Finset.range (n + 1),
        (n.choose i : ℝ) * ‖iteratedFderiv 𝕜 i f x‖ * ‖iteratedFderiv 𝕜 (n - i) g x‖ :=
  by
  simp_rw [← iteratedFderivWithin_univ]
  exact
    norm_iteratedFderivWithin_mul_le hf.cont_diff_on hg.cont_diff_on uniqueDiffOn_univ (mem_univ x)
      hn
#align norm_iterated_fderiv_mul_le norm_iteratedFderiv_mul_le

end