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

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

/-!
# Implicit function theorem

We prove three versions of the implicit function theorem. First we define a structure
`implicit_function_data` that holds arguments for the most general version of the implicit function
theorem, see `implicit_function_data.implicit_function`
and `implicit_function_data.to_implicit_function`. This version allows a user to choose
a specific implicit function but provides only a little convenience over the inverse function
theorem.

Then we define `implicit_function_of_complemented`: implicit function defined by `f (g z y) = z`,
where `f : E → F` is a function strictly differentiable at `a` such that its derivative `f'`
is surjective and has a `complemented` kernel.

Finally, if the codomain of `f` is a finite dimensional space, then we can automatically prove
that the kernel of `f'` is complemented, hence the only assumptions are `has_strict_fderiv_at`
and `f'.range = ⊤`. This version is named `implicit_function`.

## TODO

* Add a version for a function `f : E × F → G` such that $$\frac{\partial f}{\partial y}$$ is
  invertible.
* Add a version for `f : 𝕜 × 𝕜 → 𝕜` proving `has_strict_deriv_at` and `deriv φ = ...`.
* Prove that in a real vector space the implicit function has the same smoothness as the original
  one.
* If the original function is differentiable in a neighborhood, then the implicit function is
  differentiable in a neighborhood as well. Current setup only proves differentiability at one
  point for the implicit function constructed in this file (as opposed to an unspecified implicit
  function). One of the ways to overcome this difficulty is to use uniqueness of the implicit
  function in the general version of the theorem. Another way is to prove that *any* implicit
  function satisfying some predicate is strictly differentiable.

## Tags

implicit function, inverse function
-/

noncomputable section

open Topology

open Filter

open ContinuousLinearMap (fst snd smul_right ker_prod)

open ContinuousLinearEquiv (ofBijective)

open LinearMap (ker range)

/-!
### General version

Consider two functions `f : E → F` and `g : E → G` and a point `a` such that

* both functions are strictly differentiable at `a`;
* the derivatives are surjective;
* the kernels of the derivatives are complementary subspaces of `E`.

Note that the map `x ↦ (f x, g x)` has a bijective derivative, hence it is a local homeomorphism
between `E` and `F × G`. We use this fact to define a function `φ : F → G → E`
(see `implicit_function_data.implicit_function`) such that for `(y, z)` close enough to `(f a, g a)`
we have `f (φ y z) = y` and `g (φ y z) = z`.

We also prove a formula for $$\frac{\partial\varphi}{\partial z}.$$

Though this statement is almost symmetric with respect to `F`, `G`, we interpret it in the following
way. Consider a family of surfaces `{x | f x = y}`, `y ∈ 𝓝 (f a)`. Each of these surfaces is
parametrized by `φ y`.

There are many ways to choose a (differentiable) function `φ` such that `f (φ y z) = y` but the
extra condition `g (φ y z) = z` allows a user to select one of these functions. If we imagine
that the level surfaces `f = const` form a local horizontal foliation, then the choice of
`g` fixes a transverse foliation `g = const`, and `φ` is the inverse function of the projection
of `{x | f x = y}` along this transverse foliation.

This version of the theorem is used to prove the other versions and can be used if a user
needs to have a complete control over the choice of the implicit function.
-/

/-- Data for the general version of the implicit function theorem. It holds two functions
`f : E → F` and `g : E → G` (named `left_fun` and `right_fun`) and a point `a` (named `pt`)
such that

* both functions are strictly differentiable at `a`;
* the derivatives are surjective;
* the kernels of the derivatives are complementary subspaces of `E`. -/
@[nolint has_nonempty_instance]
structure ImplicitFunctionData (𝕜 : Type _) [NontriviallyNormedField 𝕜] (E : Type _)
  [NormedAddCommGroup E] [NormedSpace 𝕜 E] [CompleteSpace E] (F : Type _) [NormedAddCommGroup F]
  [NormedSpace 𝕜 F] [CompleteSpace F] (G : Type _) [NormedAddCommGroup G] [NormedSpace 𝕜 G]
  [CompleteSpace G] where
  leftFun : E → F
  leftDeriv : E →L[𝕜] F
  rightFun : E → G
  rightDeriv : E →L[𝕜] G
  pt : E
  left_has_deriv : HasStrictFderivAt left_fun left_deriv pt
  right_has_deriv : HasStrictFderivAt right_fun right_deriv pt
  left_range : range left_deriv = ⊤
  right_range : range right_deriv = ⊤
  isCompl_ker : IsCompl (ker left_deriv) (ker right_deriv)
#align implicit_function_data ImplicitFunctionData

namespace ImplicitFunctionData

variable {𝕜 : Type _} [NontriviallyNormedField 𝕜] {E : Type _} [NormedAddCommGroup E]
  [NormedSpace 𝕜 E] [CompleteSpace E] {F : Type _} [NormedAddCommGroup F] [NormedSpace 𝕜 F]
  [CompleteSpace F] {G : Type _} [NormedAddCommGroup G] [NormedSpace 𝕜 G] [CompleteSpace G]
  (φ : ImplicitFunctionData 𝕜 E F G)

/-- The function given by `x ↦ (left_fun x, right_fun x)`. -/
def prodFun (x : E) : F × G :=
  (φ.leftFun x, φ.rightFun x)
#align implicit_function_data.prod_fun ImplicitFunctionData.prodFun

@[simp]
theorem prodFun_apply (x : E) : φ.prodFun x = (φ.leftFun x, φ.rightFun x) :=
  rfl
#align implicit_function_data.prod_fun_apply ImplicitFunctionData.prodFun_apply

protected theorem hasStrictFderivAt :
    HasStrictFderivAt φ.prodFun
      (φ.leftDeriv.equivProdOfSurjectiveOfIsCompl φ.rightDeriv φ.left_range φ.right_range
          φ.isCompl_ker :
        E →L[𝕜] F × G)
      φ.pt :=
  φ.left_has_deriv.Prod φ.right_has_deriv
#align implicit_function_data.has_strict_fderiv_at ImplicitFunctionData.hasStrictFderivAt

/-- Implicit function theorem. If `f : E → F` and `g : E → G` are two maps strictly differentiable
at `a`, their derivatives `f'`, `g'` are surjective, and the kernels of these derivatives are
complementary subspaces of `E`, then `x ↦ (f x, g x)` defines a local homeomorphism between
`E` and `F × G`. In particular, `{x | f x = f a}` is locally homeomorphic to `G`. -/
def toLocalHomeomorph : LocalHomeomorph E (F × G) :=
  φ.HasStrictFderivAt.toLocalHomeomorph _
#align implicit_function_data.to_local_homeomorph ImplicitFunctionData.toLocalHomeomorph

/-- Implicit function theorem. If `f : E → F` and `g : E → G` are two maps strictly differentiable
at `a`, their derivatives `f'`, `g'` are surjective, and the kernels of these derivatives are
complementary subspaces of `E`, then `implicit_function_of_is_compl_ker` is the unique (germ of a)
map `φ : F → G → E` such that `f (φ y z) = y` and `g (φ y z) = z`. -/
def implicitFunction : F → G → E :=
  Function.curry <| φ.toLocalHomeomorph.symm
#align implicit_function_data.implicit_function ImplicitFunctionData.implicitFunction

@[simp]
theorem toLocalHomeomorph_coe : ⇑φ.toLocalHomeomorph = φ.prodFun :=
  rfl
#align implicit_function_data.to_local_homeomorph_coe ImplicitFunctionData.toLocalHomeomorph_coe

theorem toLocalHomeomorph_apply (x : E) : φ.toLocalHomeomorph x = (φ.leftFun x, φ.rightFun x) :=
  rfl
#align implicit_function_data.to_local_homeomorph_apply ImplicitFunctionData.toLocalHomeomorph_apply

theorem pt_mem_toLocalHomeomorph_source : φ.pt ∈ φ.toLocalHomeomorph.source :=
  φ.HasStrictFderivAt.mem_toLocalHomeomorph_source
#align implicit_function_data.pt_mem_to_local_homeomorph_source ImplicitFunctionData.pt_mem_toLocalHomeomorph_source

theorem map_pt_mem_toLocalHomeomorph_target :
    (φ.leftFun φ.pt, φ.rightFun φ.pt) ∈ φ.toLocalHomeomorph.target :=
  φ.toLocalHomeomorph.map_source <| φ.pt_mem_toLocalHomeomorph_source
#align implicit_function_data.map_pt_mem_to_local_homeomorph_target ImplicitFunctionData.map_pt_mem_toLocalHomeomorph_target

theorem prod_map_implicitFunction :
    ∀ᶠ p : F × G in 𝓝 (φ.prodFun φ.pt), φ.prodFun (φ.implicitFunction p.1 p.2) = p :=
  φ.HasStrictFderivAt.eventually_right_inverse.mono fun ⟨z, y⟩ h => h
#align implicit_function_data.prod_map_implicit_function ImplicitFunctionData.prod_map_implicitFunction

theorem left_map_implicitFunction :
    ∀ᶠ p : F × G in 𝓝 (φ.prodFun φ.pt), φ.leftFun (φ.implicitFunction p.1 p.2) = p.1 :=
  φ.prod_map_implicitFunction.mono fun z => congr_arg Prod.fst
#align implicit_function_data.left_map_implicit_function ImplicitFunctionData.left_map_implicitFunction

theorem right_map_implicitFunction :
    ∀ᶠ p : F × G in 𝓝 (φ.prodFun φ.pt), φ.rightFun (φ.implicitFunction p.1 p.2) = p.2 :=
  φ.prod_map_implicitFunction.mono fun z => congr_arg Prod.snd
#align implicit_function_data.right_map_implicit_function ImplicitFunctionData.right_map_implicitFunction

theorem implicitFunction_apply_image :
    ∀ᶠ x in 𝓝 φ.pt, φ.implicitFunction (φ.leftFun x) (φ.rightFun x) = x :=
  φ.HasStrictFderivAt.eventually_left_inverse
#align implicit_function_data.implicit_function_apply_image ImplicitFunctionData.implicitFunction_apply_image

theorem map_nhds_eq : map φ.leftFun (𝓝 φ.pt) = 𝓝 (φ.leftFun φ.pt) :=
  show map (Prod.fst ∘ φ.prodFun) (𝓝 φ.pt) = 𝓝 (φ.prodFun φ.pt).1 by
    rw [← map_map, φ.has_strict_fderiv_at.map_nhds_eq_of_equiv, map_fst_nhds]
#align implicit_function_data.map_nhds_eq ImplicitFunctionData.map_nhds_eq

theorem implicitFunction_hasStrictFderivAt (g'inv : G →L[𝕜] E)
    (hg'inv : φ.rightDeriv.comp g'inv = ContinuousLinearMap.id 𝕜 G)
    (hg'invf : φ.leftDeriv.comp g'inv = 0) :
    HasStrictFderivAt (φ.implicitFunction (φ.leftFun φ.pt)) g'inv (φ.rightFun φ.pt) :=
  by
  have := φ.has_strict_fderiv_at.to_local_inverse
  simp only [prod_fun] at this
  convert this.comp (φ.right_fun φ.pt) ((hasStrictFderivAt_const _ _).Prod (hasStrictFderivAt_id _))
  simp only [ContinuousLinearMap.ext_iff, ContinuousLinearMap.coe_comp', Function.comp_apply] at
    hg'inv hg'invf⊢
  simp [ContinuousLinearEquiv.eq_symm_apply, *]
#align implicit_function_data.implicit_function_has_strict_fderiv_at ImplicitFunctionData.implicitFunction_hasStrictFderivAt

end ImplicitFunctionData

namespace HasStrictFderivAt

section Complemented

/-!
### Case of a complemented kernel

In this section we prove the following version of the implicit function theorem. Consider a map
`f : E → F` and a point `a : E` such that `f` is strictly differentiable at `a`, its derivative `f'`
is surjective and the kernel of `f'` is a complemented subspace of `E` (i.e., it has a closed
complementary subspace). Then there exists a function `φ : F → ker f' → E` such that for `(y, z)`
close to `(f a, 0)` we have `f (φ y z) = y` and the derivative of `φ (f a)` at zero is the
embedding `ker f' → E`.

Note that a map with these properties is not unique. E.g., different choices of a subspace
complementary to `ker f'` lead to different maps `φ`.
-/

variable {𝕜 : Type _} [NontriviallyNormedField 𝕜] {E : Type _} [NormedAddCommGroup E]
  [NormedSpace 𝕜 E] [CompleteSpace E] {F : Type _} [NormedAddCommGroup F] [NormedSpace 𝕜 F]
  [CompleteSpace F] {f : E → F} {f' : E →L[𝕜] F} {a : E}

section Defs

variable (f f')

/-- Data used to apply the generic implicit function theorem to the case of a strictly
differentiable map such that its derivative is surjective and has a complemented kernel. -/
@[simp]
def implicitFunctionDataOfComplemented (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤)
    (hker : (ker f').ClosedComplemented) : ImplicitFunctionData 𝕜 E F (ker f')
    where
  leftFun := f
  leftDeriv := f'
  rightFun x := Classical.choose hker (x - a)
  rightDeriv := Classical.choose hker
  pt := a
  left_has_deriv := hf
  right_has_deriv :=
    (Classical.choose hker).HasStrictFderivAt.comp a ((hasStrictFderivAt_id a).sub_const a)
  left_range := hf'
  right_range := LinearMap.range_eq_of_proj (Classical.choose_spec hker)
  isCompl_ker := LinearMap.isCompl_of_proj (Classical.choose_spec hker)
#align has_strict_fderiv_at.implicit_function_data_of_complemented HasStrictFderivAt.implicitFunctionDataOfComplemented

/-- A local homeomorphism between `E` and `F × f'.ker` sending level surfaces of `f`
to vertical subspaces. -/
def implicitToLocalHomeomorphOfComplemented (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤)
    (hker : (ker f').ClosedComplemented) : LocalHomeomorph E (F × ker f') :=
  (implicitFunctionDataOfComplemented f f' hf hf' hker).toLocalHomeomorph
#align has_strict_fderiv_at.implicit_to_local_homeomorph_of_complemented HasStrictFderivAt.implicitToLocalHomeomorphOfComplemented

/-- Implicit function `g` defined by `f (g z y) = z`. -/
def implicitFunctionOfComplemented (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤)
    (hker : (ker f').ClosedComplemented) : F → ker f' → E :=
  (implicitFunctionDataOfComplemented f f' hf hf' hker).implicitFunction
#align has_strict_fderiv_at.implicit_function_of_complemented HasStrictFderivAt.implicitFunctionOfComplemented

end Defs

@[simp]
theorem implicitToLocalHomeomorphOfComplemented_fst (hf : HasStrictFderivAt f f' a)
    (hf' : range f' = ⊤) (hker : (ker f').ClosedComplemented) (x : E) :
    (hf.implicitToLocalHomeomorphOfComplemented f f' hf' hker x).fst = f x :=
  rfl
#align has_strict_fderiv_at.implicit_to_local_homeomorph_of_complemented_fst HasStrictFderivAt.implicitToLocalHomeomorphOfComplemented_fst

theorem implicitToLocalHomeomorphOfComplemented_apply (hf : HasStrictFderivAt f f' a)
    (hf' : range f' = ⊤) (hker : (ker f').ClosedComplemented) (y : E) :
    hf.implicitToLocalHomeomorphOfComplemented f f' hf' hker y =
      (f y, Classical.choose hker (y - a)) :=
  rfl
#align has_strict_fderiv_at.implicit_to_local_homeomorph_of_complemented_apply HasStrictFderivAt.implicitToLocalHomeomorphOfComplemented_apply

@[simp]
theorem implicitToLocalHomeomorphOfComplemented_apply_ker (hf : HasStrictFderivAt f f' a)
    (hf' : range f' = ⊤) (hker : (ker f').ClosedComplemented) (y : ker f') :
    hf.implicitToLocalHomeomorphOfComplemented f f' hf' hker (y + a) = (f (y + a), y) := by
  simp only [implicit_to_local_homeomorph_of_complemented_apply, add_sub_cancel,
    Classical.choose_spec hker]
#align has_strict_fderiv_at.implicit_to_local_homeomorph_of_complemented_apply_ker HasStrictFderivAt.implicitToLocalHomeomorphOfComplemented_apply_ker

@[simp]
theorem implicitToLocalHomeomorphOfComplemented_self (hf : HasStrictFderivAt f f' a)
    (hf' : range f' = ⊤) (hker : (ker f').ClosedComplemented) :
    hf.implicitToLocalHomeomorphOfComplemented f f' hf' hker a = (f a, 0) := by
  simp [hf.implicit_to_local_homeomorph_of_complemented_apply]
#align has_strict_fderiv_at.implicit_to_local_homeomorph_of_complemented_self HasStrictFderivAt.implicitToLocalHomeomorphOfComplemented_self

theorem mem_implicitToLocalHomeomorphOfComplemented_source (hf : HasStrictFderivAt f f' a)
    (hf' : range f' = ⊤) (hker : (ker f').ClosedComplemented) :
    a ∈ (hf.implicitToLocalHomeomorphOfComplemented f f' hf' hker).source :=
  mem_toLocalHomeomorph_source _
#align has_strict_fderiv_at.mem_implicit_to_local_homeomorph_of_complemented_source HasStrictFderivAt.mem_implicitToLocalHomeomorphOfComplemented_source

theorem mem_implicitToLocalHomeomorphOfComplemented_target (hf : HasStrictFderivAt f f' a)
    (hf' : range f' = ⊤) (hker : (ker f').ClosedComplemented) :
    (f a, (0 : ker f')) ∈ (hf.implicitToLocalHomeomorphOfComplemented f f' hf' hker).target := by
  simpa only [implicit_to_local_homeomorph_of_complemented_self] using
    (hf.implicit_to_local_homeomorph_of_complemented f f' hf' hker).map_source <|
      hf.mem_implicit_to_local_homeomorph_of_complemented_source hf' hker
#align has_strict_fderiv_at.mem_implicit_to_local_homeomorph_of_complemented_target HasStrictFderivAt.mem_implicitToLocalHomeomorphOfComplemented_target

/-- `implicit_function_of_complemented` sends `(z, y)` to a point in `f ⁻¹' z`. -/
theorem map_implicitFunctionOfComplemented_eq (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤)
    (hker : (ker f').ClosedComplemented) :
    ∀ᶠ p : F × ker f' in 𝓝 (f a, 0),
      f (hf.implicitFunctionOfComplemented f f' hf' hker p.1 p.2) = p.1 :=
  ((hf.implicitToLocalHomeomorphOfComplemented f f' hf' hker).eventually_right_inverse <|
        hf.mem_implicitToLocalHomeomorphOfComplemented_target hf' hker).mono
    fun ⟨z, y⟩ h => congr_arg Prod.fst h
#align has_strict_fderiv_at.map_implicit_function_of_complemented_eq HasStrictFderivAt.map_implicitFunctionOfComplemented_eq

/-- Any point in some neighborhood of `a` can be represented as `implicit_function`
of some point. -/
theorem eq_implicitFunctionOfComplemented (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤)
    (hker : (ker f').ClosedComplemented) :
    ∀ᶠ x in 𝓝 a,
      hf.implicitFunctionOfComplemented f f' hf' hker (f x)
          (hf.implicitToLocalHomeomorphOfComplemented f f' hf' hker x).snd =
        x :=
  (implicitFunctionDataOfComplemented f f' hf hf' hker).implicitFunction_apply_image
#align has_strict_fderiv_at.eq_implicit_function_of_complemented HasStrictFderivAt.eq_implicitFunctionOfComplemented

@[simp]
theorem implicitFunctionOfComplemented_apply_image (hf : HasStrictFderivAt f f' a)
    (hf' : range f' = ⊤) (hker : (ker f').ClosedComplemented) :
    hf.implicitFunctionOfComplemented f f' hf' hker (f a) 0 = a :=
  by
  convert(hf.implicit_to_local_homeomorph_of_complemented f f' hf' hker).left_inv
      (hf.mem_implicit_to_local_homeomorph_of_complemented_source hf' hker)
  exact congr_arg Prod.snd (hf.implicit_to_local_homeomorph_of_complemented_self hf' hker).symm
#align has_strict_fderiv_at.implicit_function_of_complemented_apply_image HasStrictFderivAt.implicitFunctionOfComplemented_apply_image

theorem to_implicitFunctionOfComplemented (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤)
    (hker : (ker f').ClosedComplemented) :
    HasStrictFderivAt (hf.implicitFunctionOfComplemented f f' hf' hker (f a)) (ker f').subtypeL 0 :=
  by
  convert(implicit_function_data_of_complemented f f' hf hf'
          hker).implicitFunction_hasStrictFderivAt
      (ker f').subtypeL _ _
  swap
  · ext
    simp only [Classical.choose_spec hker, implicit_function_data_of_complemented,
      ContinuousLinearMap.coe_comp', Submodule.coe_subtypeL', Submodule.coeSubtype,
      Function.comp_apply, ContinuousLinearMap.coe_id', id.def]
  swap
  · ext
    simp only [ContinuousLinearMap.coe_comp', Submodule.coe_subtypeL', Submodule.coeSubtype,
      Function.comp_apply, LinearMap.map_coe_ker, ContinuousLinearMap.zero_apply]
  simp only [implicit_function_data_of_complemented, map_sub, sub_self]
#align has_strict_fderiv_at.to_implicit_function_of_complemented HasStrictFderivAt.to_implicitFunctionOfComplemented

end Complemented

/-!
### Finite dimensional case

In this section we prove the following version of the implicit function theorem. Consider a map
`f : E → F` from a Banach normed space to a finite dimensional space.
Take a point `a : E` such that `f` is strictly differentiable at `a` and its derivative `f'`
is surjective. Then there exists a function `φ : F → ker f' → E` such that for `(y, z)`
close to `(f a, 0)` we have `f (φ y z) = y` and the derivative of `φ (f a)` at zero is the
embedding `ker f' → E`.

This version deduces that `ker f'` is a complemented subspace from the fact that `F` is a finite
dimensional space, then applies the previous version.

Note that a map with these properties is not unique. E.g., different choices of a subspace
complementary to `ker f'` lead to different maps `φ`.
-/

section FiniteDimensional

variable {𝕜 : Type _} [NontriviallyNormedField 𝕜] [CompleteSpace 𝕜] {E : Type _}
  [NormedAddCommGroup E] [NormedSpace 𝕜 E] [CompleteSpace E] {F : Type _} [NormedAddCommGroup F]
  [NormedSpace 𝕜 F] [FiniteDimensional 𝕜 F] (f : E → F) (f' : E →L[𝕜] F) {a : E}

/-- Given a map `f : E → F` to a finite dimensional space with a surjective derivative `f'`,
returns a local homeomorphism between `E` and `F × ker f'`. -/
def implicitToLocalHomeomorph (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤) :
    LocalHomeomorph E (F × ker f') :=
  haveI := FiniteDimensional.complete 𝕜 F
  hf.implicit_to_local_homeomorph_of_complemented f f' hf'
    f'.ker_closed_complemented_of_finite_dimensional_range
#align has_strict_fderiv_at.implicit_to_local_homeomorph HasStrictFderivAt.implicitToLocalHomeomorph

/-- Implicit function `g` defined by `f (g z y) = z`. -/
def implicitFunction (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤) : F → ker f' → E :=
  Function.curry <| (hf.implicitToLocalHomeomorph f f' hf').symm
#align has_strict_fderiv_at.implicit_function HasStrictFderivAt.implicitFunction

variable {f f'}

@[simp]
theorem implicitToLocalHomeomorph_fst (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤) (x : E) :
    (hf.implicitToLocalHomeomorph f f' hf' x).fst = f x :=
  rfl
#align has_strict_fderiv_at.implicit_to_local_homeomorph_fst HasStrictFderivAt.implicitToLocalHomeomorph_fst

@[simp]
theorem implicitToLocalHomeomorph_apply_ker (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤)
    (y : ker f') : hf.implicitToLocalHomeomorph f f' hf' (y + a) = (f (y + a), y) := by
  apply implicit_to_local_homeomorph_of_complemented_apply_ker
#align has_strict_fderiv_at.implicit_to_local_homeomorph_apply_ker HasStrictFderivAt.implicitToLocalHomeomorph_apply_ker

@[simp]
theorem implicitToLocalHomeomorph_self (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤) :
    hf.implicitToLocalHomeomorph f f' hf' a = (f a, 0) := by
  apply implicit_to_local_homeomorph_of_complemented_self
#align has_strict_fderiv_at.implicit_to_local_homeomorph_self HasStrictFderivAt.implicitToLocalHomeomorph_self

theorem mem_implicitToLocalHomeomorph_source (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤) :
    a ∈ (hf.implicitToLocalHomeomorph f f' hf').source :=
  mem_toLocalHomeomorph_source _
#align has_strict_fderiv_at.mem_implicit_to_local_homeomorph_source HasStrictFderivAt.mem_implicitToLocalHomeomorph_source

theorem mem_implicitToLocalHomeomorph_target (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤) :
    (f a, (0 : ker f')) ∈ (hf.implicitToLocalHomeomorph f f' hf').target := by
  apply mem_implicit_to_local_homeomorph_of_complemented_target
#align has_strict_fderiv_at.mem_implicit_to_local_homeomorph_target HasStrictFderivAt.mem_implicitToLocalHomeomorph_target

theorem tendsto_implicitFunction (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤) {α : Type _}
    {l : Filter α} {g₁ : α → F} {g₂ : α → ker f'} (h₁ : Tendsto g₁ l (𝓝 <| f a))
    (h₂ : Tendsto g₂ l (𝓝 0)) :
    Tendsto (fun t => hf.implicitFunction f f' hf' (g₁ t) (g₂ t)) l (𝓝 a) :=
  by
  refine'
    ((hf.implicit_to_local_homeomorph f f' hf').tendsto_symm
          (hf.mem_implicit_to_local_homeomorph_source hf')).comp
      _
  rw [implicit_to_local_homeomorph_self]
  exact h₁.prod_mk_nhds h₂
#align has_strict_fderiv_at.tendsto_implicit_function HasStrictFderivAt.tendsto_implicitFunction

alias tendsto_implicit_function ← _root_.filter.tendsto.implicit_function
#align filter.tendsto.implicit_function Filter.Tendsto.implicitFunction

/-- `implicit_function` sends `(z, y)` to a point in `f ⁻¹' z`. -/
theorem map_implicitFunction_eq (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤) :
    ∀ᶠ p : F × ker f' in 𝓝 (f a, 0), f (hf.implicitFunction f f' hf' p.1 p.2) = p.1 := by
  apply map_implicit_function_of_complemented_eq
#align has_strict_fderiv_at.map_implicit_function_eq HasStrictFderivAt.map_implicitFunction_eq

@[simp]
theorem implicitFunction_apply_image (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤) :
    hf.implicitFunction f f' hf' (f a) 0 = a := by
  apply implicit_function_of_complemented_apply_image
#align has_strict_fderiv_at.implicit_function_apply_image HasStrictFderivAt.implicitFunction_apply_image

/-- Any point in some neighborhood of `a` can be represented as `implicit_function`
of some point. -/
theorem eq_implicitFunction (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤) :
    ∀ᶠ x in 𝓝 a,
      hf.implicitFunction f f' hf' (f x) (hf.implicitToLocalHomeomorph f f' hf' x).snd = x :=
  by apply eq_implicit_function_of_complemented
#align has_strict_fderiv_at.eq_implicit_function HasStrictFderivAt.eq_implicitFunction

theorem to_implicitFunction (hf : HasStrictFderivAt f f' a) (hf' : range f' = ⊤) :
    HasStrictFderivAt (hf.implicitFunction f f' hf' (f a)) (ker f').subtypeL 0 := by
  apply to_implicit_function_of_complemented
#align has_strict_fderiv_at.to_implicit_function HasStrictFderivAt.to_implicitFunction

end FiniteDimensional

end HasStrictFderivAt


/-
Copyright (c) 2020 Joseph Myers. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joseph Myers, Sébastien Gouëzel, Heather Macbeth
-/
import analysis.inner_product_space.projection
import analysis.normed_space.pi_Lp
import linear_algebra.finite_dimensional
import linear_algebra.unitary_group

/-!
# `L²` inner product space structure on finite products of inner product spaces

The `L²` norm on a finite product of inner product spaces is compatible with an inner product
$$
\langle x, y\rangle = \sum \langle x_i, y_i \rangle.
$$
This is recorded in this file as an inner product space instance on `pi_Lp 2`.

This file develops the notion of a finite dimensional Hilbert space over `𝕜 = ℂ, ℝ`, referred to as
`E`. We define an `orthonormal_basis 𝕜 ι E` as a linear isometric equivalence
between `E` and `euclidean_space 𝕜 ι`. Then `std_orthonormal_basis` shows that such an equivalence
always exists if `E` is finite dimensional. We provide language for converting between a basis
that is orthonormal and an orthonormal basis (e.g. `basis.to_orthonormal_basis`). We show that
orthonormal bases for each summand in a direct sum of spaces can be combined into an orthonormal
basis for the the whole sum in `direct_sum.submodule_is_internal.subordinate_orthonormal_basis`. In
the last section, various properties of matrices are explored.

## Main definitions

- `euclidean_space 𝕜 n`: defined to be `pi_Lp 2 (n → 𝕜)` for any `fintype n`, i.e., the space
  from functions to `n` to `𝕜` with the `L²` norm. We register several instances on it (notably
  that it is a finite-dimensional inner product space).

- `orthonormal_basis 𝕜 ι`: defined to be an isometry to Euclidean space from a given
  finite-dimensional innner product space, `E ≃ₗᵢ[𝕜] euclidean_space 𝕜 ι`.

- `basis.to_orthonormal_basis`: constructs an `orthonormal_basis` for a finite-dimensional
  Euclidean space from a `basis` which is `orthonormal`.

- `orthonormal.exists_orthonormal_basis_extension`: provides an existential result of an
  `orthonormal_basis` extending a given orthonormal set

- `exists_orthonormal_basis`: provides an orthonormal basis on a finite dimensional vector space

- `std_orthonormal_basis`: provides an arbitrarily-chosen `orthonormal_basis` of a given finite
  dimensional inner product space

For consequences in infinite dimension (Hilbert bases, etc.), see the file
`analysis.inner_product_space.l2_space`.

-/

open real set filter is_R_or_C submodule function
open_locale big_operators uniformity topology nnreal ennreal complex_conjugate direct_sum

noncomputable theory

variables {ι : Type*} {ι' : Type*}
variables {𝕜 : Type*} [is_R_or_C 𝕜]
variables {E : Type*} [normed_add_comm_group E] [inner_product_space 𝕜 E]
variables {E' : Type*} [normed_add_comm_group E'] [inner_product_space 𝕜 E']
variables {F : Type*} [normed_add_comm_group F] [inner_product_space ℝ F]
variables {F' : Type*} [normed_add_comm_group F'] [inner_product_space ℝ F']
local notation `⟪`x`, `y`⟫` := @inner 𝕜 _ _ x y

/-
 If `ι` is a finite type and each space `f i`, `i : ι`, is an inner product space,
then `Π i, f i` is an inner product space as well. Since `Π i, f i` is endowed with the sup norm,
we use instead `pi_Lp 2 f` for the product space, which is endowed with the `L^2` norm.
-/
instance pi_Lp.inner_product_space {ι : Type*} [fintype ι] (f : ι → Type*)
  [Π i, normed_add_comm_group (f i)] [Π i, inner_product_space 𝕜 (f i)] :
  inner_product_space 𝕜 (pi_Lp 2 f) :=
{ inner := λ x y, ∑ i, inner (x i) (y i),
  norm_sq_eq_inner := λ x,
    by simp only [pi_Lp.norm_sq_eq_of_L2, add_monoid_hom.map_sum, ← norm_sq_eq_inner, one_div],
  conj_symm :=
  begin
    intros x y,
    unfold inner,
    rw ring_hom.map_sum,
    apply finset.sum_congr rfl,
    rintros z -,
    apply inner_conj_symm,
  end,
  add_left := λ x y z,
    show ∑ i, inner (x i + y i) (z i) = ∑ i, inner (x i) (z i) + ∑ i, inner (y i) (z i),
    by simp only [inner_add_left, finset.sum_add_distrib],
  smul_left := λ x y r,
    show ∑ (i : ι), inner (r • x i) (y i) = (conj r) * ∑ i, inner (x i) (y i),
    by simp only [finset.mul_sum, inner_smul_left] }

@[simp] lemma pi_Lp.inner_apply {ι : Type*} [fintype ι] {f : ι → Type*}
  [Π i, normed_add_comm_group (f i)] [Π i, inner_product_space 𝕜 (f i)] (x y : pi_Lp 2 f) :
  ⟪x, y⟫ = ∑ i, ⟪x i, y i⟫ :=
rfl

/-- The standard real/complex Euclidean space, functions on a finite type. For an `n`-dimensional
space use `euclidean_space 𝕜 (fin n)`. -/
@[reducible, nolint unused_arguments]
def euclidean_space (𝕜 : Type*) [is_R_or_C 𝕜]
  (n : Type*) [fintype n] : Type* := pi_Lp 2 (λ (i : n), 𝕜)

lemma euclidean_space.nnnorm_eq {𝕜 : Type*} [is_R_or_C 𝕜] {n : Type*} [fintype n]
  (x : euclidean_space 𝕜 n) : ‖x‖₊ = nnreal.sqrt (∑ i, ‖x i‖₊ ^ 2) :=
pi_Lp.nnnorm_eq_of_L2 x

lemma euclidean_space.norm_eq {𝕜 : Type*} [is_R_or_C 𝕜] {n : Type*} [fintype n]
  (x : euclidean_space 𝕜 n) : ‖x‖ = real.sqrt (∑ i, ‖x i‖ ^ 2) :=
by simpa only [real.coe_sqrt, nnreal.coe_sum] using congr_arg (coe : ℝ≥0 → ℝ) x.nnnorm_eq

lemma euclidean_space.dist_eq {𝕜 : Type*} [is_R_or_C 𝕜] {n : Type*} [fintype n]
  (x y : euclidean_space 𝕜 n) : dist x y = (∑ i, dist (x i) (y i) ^ 2).sqrt :=
(pi_Lp.dist_eq_of_L2 x y : _)

lemma euclidean_space.nndist_eq {𝕜 : Type*} [is_R_or_C 𝕜] {n : Type*} [fintype n]
  (x y : euclidean_space 𝕜 n) : nndist x y = (∑ i, nndist (x i) (y i) ^ 2).sqrt :=
(pi_Lp.nndist_eq_of_L2 x y : _)

lemma euclidean_space.edist_eq {𝕜 : Type*} [is_R_or_C 𝕜] {n : Type*} [fintype n]
  (x y : euclidean_space 𝕜 n) : edist x y = (∑ i, edist (x i) (y i) ^ 2) ^ (1 / 2 : ℝ) :=
(pi_Lp.edist_eq_of_L2 x y : _)

variables [fintype ι]

section
local attribute [reducible] pi_Lp

instance : finite_dimensional 𝕜 (euclidean_space 𝕜 ι) := by apply_instance
instance : inner_product_space 𝕜 (euclidean_space 𝕜 ι) := by apply_instance

@[simp] lemma finrank_euclidean_space :
  finite_dimensional.finrank 𝕜 (euclidean_space 𝕜 ι) = fintype.card ι := by simp

lemma finrank_euclidean_space_fin {n : ℕ} :
  finite_dimensional.finrank 𝕜 (euclidean_space 𝕜 (fin n)) = n := by simp

lemma euclidean_space.inner_eq_star_dot_product (x y : euclidean_space 𝕜 ι) :
  ⟪x, y⟫ = matrix.dot_product (star $ pi_Lp.equiv _ _ x) (pi_Lp.equiv _ _ y) := rfl

lemma euclidean_space.inner_pi_Lp_equiv_symm (x y : ι → 𝕜) :
  ⟪(pi_Lp.equiv 2 _).symm x, (pi_Lp.equiv 2 _).symm y⟫ = matrix.dot_product (star x) y := rfl

/-- A finite, mutually orthogonal family of subspaces of `E`, which span `E`, induce an isometry
from `E` to `pi_Lp 2` of the subspaces equipped with the `L2` inner product. -/
def direct_sum.is_internal.isometry_L2_of_orthogonal_family
  [decidable_eq ι] {V : ι → submodule 𝕜 E} (hV : direct_sum.is_internal V)
  (hV' : orthogonal_family 𝕜 (λ i, V i) (λ i, (V i).subtypeₗᵢ)) :
  E ≃ₗᵢ[𝕜] pi_Lp 2 (λ i, V i) :=
begin
  let e₁ := direct_sum.linear_equiv_fun_on_fintype 𝕜 ι (λ i, V i),
  let e₂ := linear_equiv.of_bijective (direct_sum.coe_linear_map V) hV,
  refine linear_equiv.isometry_of_inner (e₂.symm.trans e₁) _,
  suffices : ∀ v w, ⟪v, w⟫ = ⟪e₂ (e₁.symm v), e₂ (e₁.symm w)⟫,
  { intros v₀ w₀,
    convert this (e₁ (e₂.symm v₀)) (e₁ (e₂.symm w₀));
    simp only [linear_equiv.symm_apply_apply, linear_equiv.apply_symm_apply] },
  intros v w,
  transitivity ⟪(∑ i, (V i).subtypeₗᵢ (v i)), ∑ i, (V i).subtypeₗᵢ (w i)⟫,
  { simp only [sum_inner, hV'.inner_right_fintype, pi_Lp.inner_apply] },
  { congr; simp }
end

@[simp] lemma direct_sum.is_internal.isometry_L2_of_orthogonal_family_symm_apply
  [decidable_eq ι] {V : ι → submodule 𝕜 E} (hV : direct_sum.is_internal V)
  (hV' : orthogonal_family 𝕜 (λ i, V i) (λ i, (V i).subtypeₗᵢ))
  (w : pi_Lp 2 (λ i, V i)) :
  (hV.isometry_L2_of_orthogonal_family hV').symm w = ∑ i, (w i : E) :=
begin
  classical,
  let e₁ := direct_sum.linear_equiv_fun_on_fintype 𝕜 ι (λ i, V i),
  let e₂ := linear_equiv.of_bijective (direct_sum.coe_linear_map V) hV,
  suffices : ∀ v : ⨁ i, V i, e₂ v = ∑ i, e₁ v i,
  { exact this (e₁.symm w) },
  intros v,
  simp [e₂, direct_sum.coe_linear_map, direct_sum.to_module, dfinsupp.sum_add_hom_apply]
end

end

variables (ι 𝕜)

-- TODO : This should be generalized to `pi_Lp` with finite dimensional factors.
/-- `pi_Lp.linear_equiv` upgraded to a continuous linear map between `euclidean_space 𝕜 ι`
and `ι → 𝕜`. -/
@[simps] def euclidean_space.equiv :
  euclidean_space 𝕜 ι ≃L[𝕜] (ι → 𝕜) :=
(pi_Lp.linear_equiv 2 𝕜 (λ i : ι, 𝕜)).to_continuous_linear_equiv

variables {ι 𝕜}

-- TODO : This should be generalized to `pi_Lp`.
/-- The projection on the `i`-th coordinate of `euclidean_space 𝕜 ι`, as a linear map. -/
@[simps] def euclidean_space.projₗ (i : ι) :
  euclidean_space 𝕜 ι →ₗ[𝕜] 𝕜 :=
(linear_map.proj i).comp (pi_Lp.linear_equiv 2 𝕜 (λ i : ι, 𝕜) : euclidean_space 𝕜 ι →ₗ[𝕜] ι → 𝕜)

-- TODO : This should be generalized to `pi_Lp`.
/-- The projection on the `i`-th coordinate of `euclidean_space 𝕜 ι`,
as a continuous linear map. -/
@[simps] def euclidean_space.proj (i : ι) :
  euclidean_space 𝕜 ι →L[𝕜] 𝕜 :=
⟨euclidean_space.projₗ i, continuous_apply i⟩

-- TODO : This should be generalized to `pi_Lp`.
/-- The vector given in euclidean space by being `1 : 𝕜` at coordinate `i : ι` and `0 : 𝕜` at
all other coordinates. -/
def euclidean_space.single [decidable_eq ι] (i : ι) (a : 𝕜) :
  euclidean_space 𝕜 ι :=
(pi_Lp.equiv _ _).symm (pi.single i a)

@[simp] lemma pi_Lp.equiv_single [decidable_eq ι] (i : ι) (a : 𝕜) :
  pi_Lp.equiv _ _ (euclidean_space.single i a) = pi.single i a := rfl

@[simp] lemma pi_Lp.equiv_symm_single [decidable_eq ι] (i : ι) (a : 𝕜) :
  (pi_Lp.equiv _ _).symm (pi.single i a) = euclidean_space.single i a := rfl

@[simp] theorem euclidean_space.single_apply [decidable_eq ι] (i : ι) (a : 𝕜) (j : ι) :
  (euclidean_space.single i a) j = ite (j = i) a 0 :=
by { rw [euclidean_space.single, pi_Lp.equiv_symm_apply, ← pi.single_apply i a j] }

lemma euclidean_space.inner_single_left [decidable_eq ι] (i : ι) (a : 𝕜) (v : euclidean_space 𝕜 ι) :
  ⟪euclidean_space.single i (a : 𝕜), v⟫ = conj a * (v i) :=
by simp [apply_ite conj]

lemma euclidean_space.inner_single_right [decidable_eq ι] (i : ι) (a : 𝕜)
  (v : euclidean_space 𝕜 ι) :
  ⟪v, euclidean_space.single i (a : 𝕜)⟫ =  a * conj (v i) :=
by simp [apply_ite conj, mul_comm]

lemma euclidean_space.pi_Lp_congr_left_single [decidable_eq ι] {ι' : Type*} [fintype ι']
  [decidable_eq ι'] (e : ι' ≃ ι) (i' : ι') :
  linear_isometry_equiv.pi_Lp_congr_left 2 𝕜 𝕜 e (euclidean_space.single i' (1:𝕜)) =
    euclidean_space.single (e i') (1:𝕜) :=
begin
  ext i,
  simpa using if_congr e.symm_apply_eq rfl rfl
end

variables (ι 𝕜 E)

/-- An orthonormal basis on E is an identification of `E` with its dimensional-matching
`euclidean_space 𝕜 ι`. -/
structure orthonormal_basis := of_repr :: (repr : E ≃ₗᵢ[𝕜] euclidean_space 𝕜 ι)

variables {ι 𝕜 E}

namespace orthonormal_basis

instance : inhabited (orthonormal_basis ι 𝕜 (euclidean_space 𝕜 ι)) :=
⟨of_repr (linear_isometry_equiv.refl 𝕜 (euclidean_space 𝕜 ι))⟩

/-- `b i` is the `i`th basis vector. -/
instance : has_coe_to_fun (orthonormal_basis ι 𝕜 E) (λ _, ι → E) :=
{ coe := λ b i, by classical; exact b.repr.symm (euclidean_space.single i (1 : 𝕜)) }

@[simp] lemma coe_of_repr [decidable_eq ι] (e : E ≃ₗᵢ[𝕜] euclidean_space 𝕜 ι) :
  ⇑(orthonormal_basis.of_repr e) = λ i, e.symm (euclidean_space.single i (1 : 𝕜)) :=
begin
  rw coe_fn,
  unfold has_coe_to_fun.coe,
  funext,
  congr,
  simp only [eq_iff_true_of_subsingleton],
end

@[simp] protected lemma repr_symm_single [decidable_eq ι] (b : orthonormal_basis ι 𝕜 E) (i : ι) :
  b.repr.symm (euclidean_space.single i (1:𝕜)) = b i :=
by { classical, congr, simp, }

@[simp] protected lemma repr_self [decidable_eq ι] (b : orthonormal_basis ι 𝕜 E) (i : ι) :
  b.repr (b i) = euclidean_space.single i (1:𝕜) :=
by rw [← b.repr_symm_single i, linear_isometry_equiv.apply_symm_apply]

protected lemma repr_apply_apply (b : orthonormal_basis ι 𝕜 E) (v : E) (i : ι) :
  b.repr v i = ⟪b i, v⟫ :=
begin
  classical,
  rw [← b.repr.inner_map_map (b i) v, b.repr_self i, euclidean_space.inner_single_left],
  simp only [one_mul, eq_self_iff_true, map_one],
end

@[simp]
protected lemma orthonormal (b : orthonormal_basis ι 𝕜 E) : orthonormal 𝕜 b :=
begin
  classical,
  rw orthonormal_iff_ite,
  intros i j,
  rw [← b.repr.inner_map_map (b i) (b j), b.repr_self i, b.repr_self j,
    euclidean_space.inner_single_left, euclidean_space.single_apply, map_one, one_mul],
end

/-- The `basis ι 𝕜 E` underlying the `orthonormal_basis` -/
protected def to_basis (b : orthonormal_basis ι 𝕜 E) : basis ι 𝕜 E :=
basis.of_equiv_fun b.repr.to_linear_equiv

@[simp] protected lemma coe_to_basis (b : orthonormal_basis ι 𝕜 E) :
  (⇑b.to_basis : ι → E) = ⇑b :=
begin
  change ⇑(basis.of_equiv_fun b.repr.to_linear_equiv) = b,
  ext j,
  classical,
  rw basis.coe_of_equiv_fun,
  congr,
end

@[simp] protected lemma coe_to_basis_repr (b : orthonormal_basis ι 𝕜 E) :
  b.to_basis.equiv_fun = b.repr.to_linear_equiv :=
begin
  change (basis.of_equiv_fun b.repr.to_linear_equiv).equiv_fun = b.repr.to_linear_equiv,
  ext x j,
  simp only [basis.of_equiv_fun_repr_apply, linear_isometry_equiv.coe_to_linear_equiv,
    basis.equiv_fun_apply],
end

@[simp] protected lemma coe_to_basis_repr_apply (b : orthonormal_basis ι 𝕜 E) (x : E) (i : ι) :
  b.to_basis.repr x i = b.repr x i :=
by {rw [← basis.equiv_fun_apply, orthonormal_basis.coe_to_basis_repr,
      linear_isometry_equiv.coe_to_linear_equiv]}

protected lemma sum_repr (b : orthonormal_basis ι 𝕜 E) (x : E) :
  ∑ i, b.repr x i • b i = x :=
by { simp_rw [← b.coe_to_basis_repr_apply, ← b.coe_to_basis], exact b.to_basis.sum_repr x }

protected lemma sum_repr_symm (b : orthonormal_basis ι 𝕜 E) (v : euclidean_space 𝕜 ι) :
  ∑ i , v i • b i = (b.repr.symm v) :=
by { simpa using (b.to_basis.equiv_fun_symm_apply v).symm }

protected lemma sum_inner_mul_inner (b : orthonormal_basis ι 𝕜 E) (x y : E) :
  ∑ i, ⟪x, b i⟫ * ⟪b i, y⟫ = ⟪x, y⟫ :=
begin
  have := congr_arg (innerSL 𝕜 x) (b.sum_repr y),
  rw map_sum at this,
  convert this,
  ext i,
  rw [smul_hom_class.map_smul, b.repr_apply_apply, mul_comm],
  refl,
end

protected lemma orthogonal_projection_eq_sum {U : submodule 𝕜 E} [complete_space U]
  (b : orthonormal_basis ι 𝕜 U) (x : E) :
  orthogonal_projection U x = ∑ i, ⟪(b i : E), x⟫ • b i :=
by simpa only [b.repr_apply_apply, inner_orthogonal_projection_eq_of_mem_left]
  using (b.sum_repr (orthogonal_projection U x)).symm

/-- Mapping an orthonormal basis along a `linear_isometry_equiv`. -/
protected def map {G : Type*}
  [normed_add_comm_group G] [inner_product_space 𝕜 G] (b : orthonormal_basis ι 𝕜 E)
  (L : E ≃ₗᵢ[𝕜] G) :
  orthonormal_basis ι 𝕜 G :=
{ repr := L.symm.trans b.repr }

@[simp] protected lemma map_apply {G : Type*} [normed_add_comm_group G] [inner_product_space 𝕜 G]
  (b : orthonormal_basis ι 𝕜 E) (L : E ≃ₗᵢ[𝕜] G) (i : ι) :
  b.map L i = L (b i) := rfl

@[simp] protected lemma to_basis_map {G : Type*} [normed_add_comm_group G] [inner_product_space 𝕜 G]
  (b : orthonormal_basis ι 𝕜 E) (L : E ≃ₗᵢ[𝕜] G) :
  (b.map L).to_basis = b.to_basis.map L.to_linear_equiv :=
rfl

/-- A basis that is orthonormal is an orthonormal basis. -/
def _root_.basis.to_orthonormal_basis (v : basis ι 𝕜 E) (hv : orthonormal 𝕜 v) :
  orthonormal_basis ι 𝕜 E :=
orthonormal_basis.of_repr $
linear_equiv.isometry_of_inner v.equiv_fun
begin
  intros x y,
  let p : euclidean_space 𝕜 ι := v.equiv_fun x,
  let q : euclidean_space 𝕜 ι := v.equiv_fun y,
  have key : ⟪p, q⟫ = ⟪∑ i, p i • v i, ∑ i, q i • v i⟫,
  { simp [sum_inner, inner_smul_left, hv.inner_right_fintype] },
  convert key,
  { rw [← v.equiv_fun.symm_apply_apply x, v.equiv_fun_symm_apply] },
  { rw [← v.equiv_fun.symm_apply_apply y, v.equiv_fun_symm_apply] }
end

@[simp] lemma _root_.basis.coe_to_orthonormal_basis_repr (v : basis ι 𝕜 E) (hv : orthonormal 𝕜 v) :
  ((v.to_orthonormal_basis hv).repr : E → euclidean_space 𝕜 ι) = v.equiv_fun :=
rfl

@[simp] lemma _root_.basis.coe_to_orthonormal_basis_repr_symm
  (v : basis ι 𝕜 E) (hv : orthonormal 𝕜 v) :
  ((v.to_orthonormal_basis hv).repr.symm : euclidean_space 𝕜 ι → E) = v.equiv_fun.symm :=
rfl

@[simp] lemma _root_.basis.to_basis_to_orthonormal_basis (v : basis ι 𝕜 E) (hv : orthonormal 𝕜 v) :
  (v.to_orthonormal_basis hv).to_basis = v :=
by simp [basis.to_orthonormal_basis, orthonormal_basis.to_basis]

@[simp] lemma _root_.basis.coe_to_orthonormal_basis (v : basis ι 𝕜 E) (hv : orthonormal 𝕜 v) :
  (v.to_orthonormal_basis hv : ι → E) = (v : ι → E) :=
calc (v.to_orthonormal_basis hv : ι → E) = ((v.to_orthonormal_basis hv).to_basis : ι → E) :
  by { classical, rw orthonormal_basis.coe_to_basis }
... = (v : ι → E) : by simp

variable {v : ι → E}

/-- A finite orthonormal set that spans is an orthonormal basis -/
protected def mk (hon : orthonormal 𝕜 v) (hsp: ⊤ ≤ submodule.span 𝕜 (set.range v)):
  orthonormal_basis ι 𝕜 E :=
(basis.mk (orthonormal.linear_independent hon) hsp).to_orthonormal_basis (by rwa basis.coe_mk)

@[simp]
protected lemma coe_mk (hon : orthonormal 𝕜 v) (hsp: ⊤ ≤ submodule.span 𝕜 (set.range v)) :
  ⇑(orthonormal_basis.mk hon hsp) = v :=
by classical; rw [orthonormal_basis.mk, _root_.basis.coe_to_orthonormal_basis, basis.coe_mk]

/-- Any finite subset of a orthonormal family is an `orthonormal_basis` for its span. -/
protected def span [decidable_eq E] {v' : ι' → E} (h : orthonormal 𝕜 v') (s : finset ι') :
  orthonormal_basis s 𝕜 (span 𝕜 (s.image v' : set E)) :=
let
  e₀' : basis s 𝕜 _ := basis.span (h.linear_independent.comp (coe : s → ι') subtype.coe_injective),
  e₀ : orthonormal_basis s 𝕜 _ := orthonormal_basis.mk
    begin
      convert orthonormal_span (h.comp (coe : s → ι') subtype.coe_injective),
      ext,
      simp [e₀', basis.span_apply],
    end e₀'.span_eq.ge,
  φ : span 𝕜 (s.image v' : set E) ≃ₗᵢ[𝕜] span 𝕜 (range (v' ∘ (coe : s → ι'))) :=
    linear_isometry_equiv.of_eq _ _
    begin
      rw [finset.coe_image, image_eq_range],
      refl
    end
in
e₀.map φ.symm

@[simp] protected lemma span_apply [decidable_eq E]
  {v' : ι' → E} (h : orthonormal 𝕜 v') (s : finset ι') (i : s) :
  (orthonormal_basis.span h s i : E) = v' i :=
by simp only [orthonormal_basis.span, basis.span_apply, linear_isometry_equiv.of_eq_symm,
              orthonormal_basis.map_apply, orthonormal_basis.coe_mk,
              linear_isometry_equiv.coe_of_eq_apply]

open submodule

/-- A finite orthonormal family of vectors whose span has trivial orthogonal complement is an
orthonormal basis. -/
protected def mk_of_orthogonal_eq_bot (hon : orthonormal 𝕜 v) (hsp : (span 𝕜 (set.range v))ᗮ = ⊥) :
  orthonormal_basis ι 𝕜 E :=
orthonormal_basis.mk hon
begin
  refine eq.ge _,
  haveI : finite_dimensional 𝕜 (span 𝕜 (range v)) :=
    finite_dimensional.span_of_finite 𝕜 (finite_range v),
  haveI : complete_space (span 𝕜 (range v)) := finite_dimensional.complete 𝕜 _,
  rwa orthogonal_eq_bot_iff at hsp,
end

@[simp] protected lemma coe_of_orthogonal_eq_bot_mk (hon : orthonormal 𝕜 v)
  (hsp : (span 𝕜 (set.range v))ᗮ = ⊥) :
  ⇑(orthonormal_basis.mk_of_orthogonal_eq_bot hon hsp) = v :=
orthonormal_basis.coe_mk hon _

variables [fintype ι']

/-- `b.reindex (e : ι ≃ ι')` is an `orthonormal_basis` indexed by `ι'` -/
def reindex (b : orthonormal_basis ι 𝕜 E) (e : ι ≃ ι') : orthonormal_basis ι' 𝕜 E :=
orthonormal_basis.of_repr (b.repr.trans (linear_isometry_equiv.pi_Lp_congr_left 2 𝕜 𝕜 e))

protected lemma reindex_apply (b : orthonormal_basis ι 𝕜 E) (e : ι ≃ ι') (i' : ι') :
  (b.reindex e) i' = b (e.symm i') :=
begin
  classical,
  dsimp [reindex, orthonormal_basis.has_coe_to_fun],
  rw coe_of_repr,
  dsimp,
  rw [← b.repr_symm_single, linear_isometry_equiv.pi_Lp_congr_left_symm,
    euclidean_space.pi_Lp_congr_left_single],
end

@[simp] protected lemma coe_reindex (b : orthonormal_basis ι 𝕜 E) (e : ι ≃ ι') :
  ⇑(b.reindex e) = ⇑b ∘ ⇑(e.symm) :=
funext (b.reindex_apply e)

@[simp] protected lemma repr_reindex
  (b : orthonormal_basis ι 𝕜 E) (e : ι ≃ ι') (x : E) (i' : ι') :
  (b.reindex e).repr x i' = b.repr x (e.symm i') :=
by { classical,
  rw [orthonormal_basis.repr_apply_apply, b.repr_apply_apply, orthonormal_basis.coe_reindex] }

end orthonormal_basis

/-- `![1, I]` is an orthonormal basis for `ℂ` considered as a real inner product space. -/
def complex.orthonormal_basis_one_I : orthonormal_basis (fin 2) ℝ ℂ :=
(complex.basis_one_I.to_orthonormal_basis
begin
  rw orthonormal_iff_ite,
  intros i, fin_cases i;
  intros j; fin_cases j;
  simp [real_inner_eq_re_inner]
end)

@[simp] lemma complex.orthonormal_basis_one_I_repr_apply (z : ℂ) :
  complex.orthonormal_basis_one_I.repr z =  ![z.re, z.im] :=
rfl

@[simp] lemma complex.orthonormal_basis_one_I_repr_symm_apply (x : euclidean_space ℝ (fin 2)) :
  complex.orthonormal_basis_one_I.repr.symm x = (x 0) + (x 1) * I :=
rfl

@[simp] lemma complex.to_basis_orthonormal_basis_one_I :
  complex.orthonormal_basis_one_I.to_basis = complex.basis_one_I :=
basis.to_basis_to_orthonormal_basis _ _

@[simp] lemma complex.coe_orthonormal_basis_one_I :
  (complex.orthonormal_basis_one_I : (fin 2) → ℂ) = ![1, I] :=
by simp [complex.orthonormal_basis_one_I]

/-- The isometry between `ℂ` and a two-dimensional real inner product space given by a basis. -/
def complex.isometry_of_orthonormal (v : orthonormal_basis (fin 2) ℝ F) : ℂ ≃ₗᵢ[ℝ] F :=
complex.orthonormal_basis_one_I.repr.trans v.repr.symm

@[simp] lemma complex.map_isometry_of_orthonormal (v : orthonormal_basis (fin 2) ℝ F)
  (f : F ≃ₗᵢ[ℝ] F') :
  complex.isometry_of_orthonormal (v.map f) =
    (complex.isometry_of_orthonormal v).trans f :=
by simp [complex.isometry_of_orthonormal, linear_isometry_equiv.trans_assoc, orthonormal_basis.map]

lemma complex.isometry_of_orthonormal_symm_apply
  (v : orthonormal_basis (fin 2) ℝ F) (f : F) :
  (complex.isometry_of_orthonormal v).symm f
  = (v.to_basis.coord 0 f : ℂ) + (v.to_basis.coord 1 f : ℂ) * I :=
by simp [complex.isometry_of_orthonormal]

lemma complex.isometry_of_orthonormal_apply
  (v : orthonormal_basis (fin 2) ℝ F) (z : ℂ) :
  complex.isometry_of_orthonormal v z = z.re • v 0 + z.im • v 1 :=
by simp [complex.isometry_of_orthonormal, ← v.sum_repr_symm]

open finite_dimensional

/-! ### Matrix representation of an orthonormal basis with respect to another -/

section to_matrix
variables [decidable_eq ι]

section
variables (a b : orthonormal_basis ι 𝕜 E)

/-- The change-of-basis matrix between two orthonormal bases `a`, `b` is a unitary matrix. -/
lemma orthonormal_basis.to_matrix_orthonormal_basis_mem_unitary :
  a.to_basis.to_matrix b ∈ matrix.unitary_group ι 𝕜 :=
begin
  rw matrix.mem_unitary_group_iff',
  ext i j,
  convert a.repr.inner_map_map (b i) (b j),
  rw orthonormal_iff_ite.mp b.orthonormal i j,
  refl,
end

/-- The determinant of the change-of-basis matrix between two orthonormal bases `a`, `b` has
unit length. -/
@[simp] lemma orthonormal_basis.det_to_matrix_orthonormal_basis :
  ‖a.to_basis.det b‖ = 1 :=
begin
  have : (norm_sq (a.to_basis.det b) : 𝕜) = 1,
  { simpa [is_R_or_C.mul_conj]
      using (matrix.det_of_mem_unitary (a.to_matrix_orthonormal_basis_mem_unitary b)).2 },
  norm_cast at this,
  rwa [← sqrt_norm_sq_eq_norm, sqrt_eq_one],
end

end

section real
variables (a b : orthonormal_basis ι ℝ F)

/-- The change-of-basis matrix between two orthonormal bases `a`, `b` is an orthogonal matrix. -/
lemma orthonormal_basis.to_matrix_orthonormal_basis_mem_orthogonal :
  a.to_basis.to_matrix b ∈ matrix.orthogonal_group ι ℝ :=
a.to_matrix_orthonormal_basis_mem_unitary b

/-- The determinant of the change-of-basis matrix between two orthonormal bases `a`, `b` is ±1. -/
lemma orthonormal_basis.det_to_matrix_orthonormal_basis_real :
  a.to_basis.det b = 1 ∨ a.to_basis.det b = -1 :=
begin
  rw ← sq_eq_one_iff,
  simpa [unitary, sq] using matrix.det_of_mem_unitary (a.to_matrix_orthonormal_basis_mem_unitary b)
end

end real

end to_matrix

/-! ### Existence of orthonormal basis, etc. -/

section finite_dimensional

variables {v : set E}

variables {A : ι → submodule 𝕜 E}

/-- Given an internal direct sum decomposition of a module `M`, and an orthonormal basis for each
of the components of the direct sum, the disjoint union of these orthonormal bases is an
orthonormal basis for `M`. -/
noncomputable def direct_sum.is_internal.collected_orthonormal_basis
  (hV : orthogonal_family 𝕜 (λ i, A i) (λ i, (A i).subtypeₗᵢ))
  [decidable_eq ι] (hV_sum : direct_sum.is_internal (λ i, A i)) {α : ι → Type*}
  [Π i, fintype (α i)] (v_family : Π i, orthonormal_basis (α i) 𝕜 (A i)) :
  orthonormal_basis (Σ i, α i) 𝕜 E :=
(hV_sum.collected_basis (λ i, (v_family i).to_basis)).to_orthonormal_basis $
by simpa using hV.orthonormal_sigma_orthonormal
  (show (∀ i, orthonormal 𝕜 (v_family i).to_basis), by simp)

lemma direct_sum.is_internal.collected_orthonormal_basis_mem [decidable_eq ι]
  (h : direct_sum.is_internal A) {α : ι → Type*}
  [Π i, fintype (α i)] (hV : orthogonal_family 𝕜 (λ i, A i) (λ i, (A i).subtypeₗᵢ))
  (v : Π i, orthonormal_basis (α i) 𝕜 (A i)) (a : Σ i, α i) :
  h.collected_orthonormal_basis hV v a ∈ A a.1 :=
by simp [direct_sum.is_internal.collected_orthonormal_basis]

variables [finite_dimensional 𝕜 E]

/-- In a finite-dimensional `inner_product_space`, any orthonormal subset can be extended to an
orthonormal basis. -/
lemma _root_.orthonormal.exists_orthonormal_basis_extension (hv : orthonormal 𝕜 (coe : v → E)) :
  ∃ {u : finset E} (b : orthonormal_basis u 𝕜 E), v ⊆ u ∧ ⇑b = coe :=
begin
  obtain ⟨u₀, hu₀s, hu₀, hu₀_max⟩ := exists_maximal_orthonormal hv,
  rw maximal_orthonormal_iff_orthogonal_complement_eq_bot hu₀ at hu₀_max,
  have hu₀_finite : u₀.finite := hu₀.linear_independent.finite,
  let u : finset E := hu₀_finite.to_finset,
  let fu : ↥u ≃ ↥u₀ := equiv.cast (congr_arg coe_sort hu₀_finite.coe_to_finset),
  have hfu : (coe : u → E) = (coe : u₀ → E) ∘ fu := by { ext, simp },
  have hu : orthonormal 𝕜 (coe : u → E) := by simpa [hfu] using hu₀.comp _ fu.injective,
  refine ⟨u, orthonormal_basis.mk_of_orthogonal_eq_bot hu _, _, _⟩,
  { simpa using hu₀_max },
  { simpa using hu₀s },
  { simp },
end

lemma _root_.orthonormal.exists_orthonormal_basis_extension_of_card_eq
  {ι : Type*} [fintype ι] (card_ι : finrank 𝕜 E = fintype.card ι) {v : ι → E} {s : set ι}
  (hv : orthonormal 𝕜 (s.restrict v)) :
  ∃ b : orthonormal_basis ι 𝕜 E, ∀ i ∈ s, b i = v i :=
begin
  have hsv : injective (s.restrict v) := hv.linear_independent.injective,
  have hX : orthonormal 𝕜 (coe : set.range (s.restrict v) → E),
  { rwa orthonormal_subtype_range hsv },
  obtain ⟨Y, b₀, hX, hb₀⟩ := hX.exists_orthonormal_basis_extension,
  have hιY : fintype.card ι = Y.card,
  { refine (card_ι.symm.trans _),
    exact finite_dimensional.finrank_eq_card_finset_basis b₀.to_basis },
  have hvsY : s.maps_to v Y := (s.maps_to_image v).mono_right (by rwa ← range_restrict),
  have hsv' : set.inj_on v s,
  { rw set.inj_on_iff_injective,
    exact hsv },
  obtain ⟨g, hg⟩ := hvsY.exists_equiv_extend_of_card_eq hιY hsv',
  use b₀.reindex g.symm,
  intros i hi,
  { simp [hb₀, hg i hi] },
end

variables (𝕜 E)

/-- A finite-dimensional inner product space admits an orthonormal basis. -/
lemma _root_.exists_orthonormal_basis :
  ∃ (w : finset E) (b : orthonormal_basis w 𝕜 E), ⇑b = (coe : w → E) :=
let ⟨w, hw, hw', hw''⟩ := (orthonormal_empty 𝕜 E).exists_orthonormal_basis_extension in
⟨w, hw, hw''⟩

/-- A finite-dimensional `inner_product_space` has an orthonormal basis. -/
@[irreducible] def std_orthonormal_basis : orthonormal_basis (fin (finrank 𝕜 E)) 𝕜 E :=
begin
  let b := classical.some (classical.some_spec $ exists_orthonormal_basis 𝕜 E),
  rw [finrank_eq_card_basis b.to_basis],
  exact b.reindex (fintype.equiv_fin_of_card_eq rfl),
end

/-- An orthonormal basis of `ℝ` is made either of the vector `1`, or of the vector `-1`. -/
lemma orthonormal_basis_one_dim (b : orthonormal_basis ι ℝ ℝ) :
  ⇑b = (λ _, (1 : ℝ)) ∨ ⇑b = (λ _, (-1 : ℝ)) :=
begin
  haveI : unique ι, from b.to_basis.unique,
  have : b default = 1 ∨ b default = - 1,
  { have : ‖b default‖ = 1, from b.orthonormal.1 _,
    rwa [real.norm_eq_abs, abs_eq (zero_le_one : (0 : ℝ) ≤ 1)] at this },
  rw eq_const_of_unique b,
  refine this.imp _ _; simp,
end

variables {𝕜 E}

section subordinate_orthonormal_basis
open direct_sum
variables {n : ℕ} (hn : finrank 𝕜 E = n) [decidable_eq ι]
  {V : ι → submodule 𝕜 E} (hV : is_internal V)

/-- Exhibit a bijection between `fin n` and the index set of a certain basis of an `n`-dimensional
inner product space `E`.  This should not be accessed directly, but only via the subsequent API. -/
@[irreducible] def direct_sum.is_internal.sigma_orthonormal_basis_index_equiv
  (hV' : orthogonal_family 𝕜 (λ i, V i) (λ i, (V i).subtypeₗᵢ)) :
  (Σ i, fin (finrank 𝕜 (V i))) ≃ fin n :=
let b := hV.collected_orthonormal_basis hV' (λ i, (std_orthonormal_basis 𝕜 (V i))) in
fintype.equiv_fin_of_card_eq $ (finite_dimensional.finrank_eq_card_basis b.to_basis).symm.trans hn

/-- An `n`-dimensional `inner_product_space` equipped with a decomposition as an internal direct
sum has an orthonormal basis indexed by `fin n` and subordinate to that direct sum. -/
@[irreducible] def direct_sum.is_internal.subordinate_orthonormal_basis
  (hV' : orthogonal_family 𝕜 (λ i, V i) (λ i, (V i).subtypeₗᵢ)) :
  orthonormal_basis (fin n) 𝕜 E :=
((hV.collected_orthonormal_basis hV' (λ i, (std_orthonormal_basis 𝕜 (V i)))).reindex
  (hV.sigma_orthonormal_basis_index_equiv hn hV'))

/-- An `n`-dimensional `inner_product_space` equipped with a decomposition as an internal direct
sum has an orthonormal basis indexed by `fin n` and subordinate to that direct sum. This function
provides the mapping by which it is subordinate. -/
@[irreducible] def direct_sum.is_internal.subordinate_orthonormal_basis_index
  (a : fin n) (hV' : orthogonal_family 𝕜 (λ i, V i) (λ i, (V i).subtypeₗᵢ)) : ι :=
((hV.sigma_orthonormal_basis_index_equiv hn hV').symm a).1

/-- The basis constructed in `orthogonal_family.subordinate_orthonormal_basis` is subordinate to
the `orthogonal_family` in question. -/
lemma direct_sum.is_internal.subordinate_orthonormal_basis_subordinate
  (a : fin n) (hV' : orthogonal_family 𝕜 (λ i, V i) (λ i, (V i).subtypeₗᵢ)) :
  (hV.subordinate_orthonormal_basis hn hV' a) ∈
  V (hV.subordinate_orthonormal_basis_index hn a hV') :=
by simpa only [direct_sum.is_internal.subordinate_orthonormal_basis,
  orthonormal_basis.coe_reindex, direct_sum.is_internal.subordinate_orthonormal_basis_index]
  using hV.collected_orthonormal_basis_mem hV' (λ i, (std_orthonormal_basis 𝕜 (V i)))
    ((hV.sigma_orthonormal_basis_index_equiv hn hV').symm a)

end subordinate_orthonormal_basis

end finite_dimensional

local attribute [instance] fact_finite_dimensional_of_finrank_eq_succ

/-- Given a natural number `n` one less than the `finrank` of a finite-dimensional inner product
space, there exists an isometry from the orthogonal complement of a nonzero singleton to
`euclidean_space 𝕜 (fin n)`. -/
def orthonormal_basis.from_orthogonal_span_singleton
  (n : ℕ) [fact (finrank 𝕜 E = n + 1)] {v : E} (hv : v ≠ 0) :
  orthonormal_basis (fin n) 𝕜 (𝕜 ∙ v)ᗮ :=
(std_orthonormal_basis _ _).reindex $ fin_congr $ finrank_orthogonal_span_singleton hv

section linear_isometry

variables {V : Type*} [normed_add_comm_group V] [inner_product_space 𝕜 V] [finite_dimensional 𝕜 V]

variables {S : submodule 𝕜 V} {L : S →ₗᵢ[𝕜] V}

open finite_dimensional

/-- Let `S` be a subspace of a finite-dimensional complex inner product space `V`.  A linear
isometry mapping `S` into `V` can be extended to a full isometry of `V`.

TODO:  The case when `S` is a finite-dimensional subspace of an infinite-dimensional `V`.-/
noncomputable def linear_isometry.extend (L : S →ₗᵢ[𝕜] V): V →ₗᵢ[𝕜] V :=
begin
  -- Build an isometry from Sᗮ to L(S)ᗮ through euclidean_space
  let d := finrank 𝕜 Sᗮ,
  have dim_S_perp : finrank 𝕜 Sᗮ = d := rfl,
  let LS := L.to_linear_map.range,
  have E : Sᗮ ≃ₗᵢ[𝕜] LSᗮ,
  { have dim_LS_perp : finrank 𝕜 LSᗮ = d,
    calc  finrank 𝕜 LSᗮ = finrank 𝕜 V - finrank 𝕜 LS : by simp only
        [← LS.finrank_add_finrank_orthogonal, add_tsub_cancel_left]
      ...               = finrank 𝕜 V - finrank 𝕜 S : by simp only
        [linear_map.finrank_range_of_inj L.injective]
      ...               = finrank 𝕜 Sᗮ : by simp only
        [← S.finrank_add_finrank_orthogonal, add_tsub_cancel_left],
    exact (std_orthonormal_basis 𝕜 Sᗮ).repr.trans
      ((std_orthonormal_basis 𝕜 LSᗮ).reindex $ fin_congr dim_LS_perp).repr.symm },
  let L3 := (LS)ᗮ.subtypeₗᵢ.comp E.to_linear_isometry,
  -- Project onto S and Sᗮ
  haveI : complete_space S := finite_dimensional.complete 𝕜 S,
  haveI : complete_space V := finite_dimensional.complete 𝕜 V,
  let p1 := (orthogonal_projection S).to_linear_map,
  let p2 := (orthogonal_projection Sᗮ).to_linear_map,
  -- Build a linear map from the isometries on S and Sᗮ
  let M := L.to_linear_map.comp p1 + L3.to_linear_map.comp p2,
  -- Prove that M is an isometry
  have M_norm_map : ∀ (x : V), ‖M x‖ = ‖x‖,
  { intro x,
    -- Apply M to the orthogonal decomposition of x
    have Mx_decomp : M x = L (p1 x) + L3 (p2 x),
    { simp only [linear_map.add_apply, linear_map.comp_apply, linear_map.comp_apply,
      linear_isometry.coe_to_linear_map]},
    -- Mx_decomp is the orthogonal decomposition of M x
    have Mx_orth : ⟪ L (p1 x), L3 (p2 x) ⟫ = 0,
    { have Lp1x : L (p1 x) ∈ L.to_linear_map.range :=
        linear_map.mem_range_self L.to_linear_map (p1 x),
      have Lp2x : L3 (p2 x) ∈ (L.to_linear_map.range)ᗮ,
      { simp only [L3, linear_isometry.coe_comp, function.comp_app, submodule.coe_subtypeₗᵢ,
          ← submodule.range_subtype (LSᗮ)],
        apply linear_map.mem_range_self},
      apply submodule.inner_right_of_mem_orthogonal Lp1x Lp2x},
    -- Apply the Pythagorean theorem and simplify
    rw [← sq_eq_sq (norm_nonneg _) (norm_nonneg _), norm_sq_eq_add_norm_sq_projection x S],
    simp only [sq, Mx_decomp],
    rw norm_add_sq_eq_norm_sq_add_norm_sq_of_inner_eq_zero (L (p1 x)) (L3 (p2 x)) Mx_orth,
    simp only [linear_isometry.norm_map, p1, p2, continuous_linear_map.to_linear_map_eq_coe,
      add_left_inj, mul_eq_mul_left_iff, norm_eq_zero, true_or, eq_self_iff_true,
      continuous_linear_map.coe_coe, submodule.coe_norm, submodule.coe_eq_zero] },
  exact { to_linear_map := M, norm_map' := M_norm_map },
end

lemma linear_isometry.extend_apply (L : S →ₗᵢ[𝕜] V) (s : S):
  L.extend s = L s :=
begin
  haveI : complete_space S := finite_dimensional.complete 𝕜 S,
  simp only [linear_isometry.extend, continuous_linear_map.to_linear_map_eq_coe,
    ←linear_isometry.coe_to_linear_map],
  simp only [add_right_eq_self, linear_isometry.coe_to_linear_map,
    linear_isometry_equiv.coe_to_linear_isometry, linear_isometry.coe_comp, function.comp_app,
    orthogonal_projection_mem_subspace_eq_self, linear_map.coe_comp, continuous_linear_map.coe_coe,
    submodule.coe_subtype, linear_map.add_apply, submodule.coe_eq_zero,
    linear_isometry_equiv.map_eq_zero_iff, submodule.coe_subtypeₗᵢ,
    orthogonal_projection_mem_subspace_orthogonal_complement_eq_zero,
    submodule.orthogonal_orthogonal, submodule.coe_mem],
end

end linear_isometry

section matrix

open_locale matrix

variables {m n : Type*}

namespace matrix
variables [fintype m] [fintype n] [decidable_eq n]

/-- `matrix.to_lin'` adapted for `euclidean_space 𝕜 _`. -/
def to_euclidean_lin : matrix m n 𝕜 ≃ₗ[𝕜] (euclidean_space 𝕜 n →ₗ[𝕜] euclidean_space 𝕜 m) :=
matrix.to_lin' ≪≫ₗ linear_equiv.arrow_congr
  (pi_Lp.linear_equiv _ 𝕜 (λ _ : n, 𝕜)).symm (pi_Lp.linear_equiv _ 𝕜 (λ _ : m, 𝕜)).symm

@[simp]
lemma to_euclidean_lin_pi_Lp_equiv_symm (A : matrix m n 𝕜) (x : n → 𝕜) :
  A.to_euclidean_lin ((pi_Lp.equiv _ _).symm x) = (pi_Lp.equiv _ _).symm (A.to_lin' x) := rfl

@[simp]
lemma pi_Lp_equiv_to_euclidean_lin (A : matrix m n 𝕜) (x : euclidean_space 𝕜 n) :
  pi_Lp.equiv _ _ (A.to_euclidean_lin x) = A.to_lin' (pi_Lp.equiv _ _ x) := rfl

/- `matrix.to_euclidean_lin` is the same as `matrix.to_lin` applied to `pi_Lp.basis_fun`, -/
lemma to_euclidean_lin_eq_to_lin :
  (to_euclidean_lin : matrix m n 𝕜 ≃ₗ[𝕜] _) =
    matrix.to_lin (pi_Lp.basis_fun _ _ _) (pi_Lp.basis_fun _ _ _) := rfl

end matrix

local notation `⟪`x`, `y`⟫ₑ` := @inner 𝕜 _ _ ((pi_Lp.equiv 2 _).symm x) ((pi_Lp.equiv 2 _).symm y)

/-- The inner product of a row of `A` and a row of `B` is an entry of `B ⬝ Aᴴ`. -/
lemma inner_matrix_row_row [fintype n] (A B : matrix m n 𝕜) (i j : m) :
  ⟪A i, B j⟫ₑ = (B ⬝ Aᴴ) j i :=
by simp_rw [euclidean_space.inner_pi_Lp_equiv_symm, matrix.mul_apply', matrix.dot_product_comm,
  matrix.conj_transpose_apply, pi.star_def]

/-- The inner product of a column of `A` and a column of `B` is an entry of `Aᴴ ⬝ B`. -/
lemma inner_matrix_col_col [fintype m] (A B : matrix m n 𝕜) (i j : n) :
  ⟪Aᵀ i, Bᵀ j⟫ₑ = (Aᴴ ⬝ B) i j := rfl

end matrix


import .filter
open set filter lattice
universes u v w

class topology (α : Type u) :=
(𝒪 : set (set α))
(top : univ ∈ 𝒪)
(i : ∀ U V ∈ 𝒪, U ∩ V ∈ 𝒪)
(U : ∀ ℱ ⊆ 𝒪, ⋃₀ ℱ ∈ 𝒪)

namespace topology

variables {X : Type u} [topology X]
variables {Y : Type u} [topology Y]
lemma ext {X : Type u} : ∀ {τ₁ τ₂ : topology X}, @topology.𝒪 _ τ₁ = @topology.𝒪 _ τ₂ → τ₁ = τ₂
|⟨O,_,_,_⟩ ⟨_,_,_,_⟩ rfl := rfl
lemma has_empty : ∅ ∈ 𝒪(X) :=
    have s:(⋃₀ ∅ ∈ 𝒪(X)), from U ∅ (λ _ y,false.rec_on _ y),
    by rw <-union.empty; exact s
def is_open {X : Type u} (τ : topology X) (U : set X) : Prop := U ∈ 𝒪(X)
def is_closed (V : set X) : Prop :=  (-V) ∈ 𝒪(X)
/--Set of closed subsets of topology X -/
def 𝒞 : set (set X) := {V | (-V) ∈ 𝒪(X)}

def nhds (x : X) : filter X := ⨅₀ (principal <$> {U|x ∈ U ∧ U ∈ 𝒪(X)})
lemma nhds_have_x {x : X} {U:set X} {h : U ∈ nhds x} : x ∈ U
:= free_gen.rec_on h
    (λ V ⟨f,⟨W,⟨q,oW⟩,p⟩,j⟩ ,begin rw <-p at j, apply j q end)
    (⟨⟩)
    (λ A B _ ss xA, ss xA)
    (λ A B _ _ xA xB,⟨xA,xB⟩)

def compact (S : set X) := ∀ℱ, ℱ ≠ ⊥ → ℱ ≤ principal S → ∃ s ∈ S, ℱ ⊓ nhds s ≠ ⊥
def hausdorff (X : Type u) [topology X] := ∀ u v : X, ∃ (U ∈ 𝒪(X)) (V ∈ 𝒪(X)), u ∈ U ∧ v ∈ V ∧ U ∩ V = ∅
instance (α : Type u) : partial_order (topology α) :=
{ le := λ X Y, @topology.𝒪 _ X ⊆ @topology.𝒪 _ Y
, le_refl := λ a, subset.refl
, le_trans := λ a b c ab bc, subset.trans ab bc
, le_antisymm := λ a b ab ba, topology.ext $ subset.antisymm ab ba
}
def indiscrete (α : Type u) : topology α :=
{ 𝒪 := {U|U = ∅ ∨ U = univ}
, top := or.inr rfl
, i := λ U V hu hv, begin cases hu, all_goals{ cases hv}, all_goals {rw hu, rw hv}, repeat{apply or.inl inter.empty}, apply or.inl, rw inter_comm, apply inter.empty, exact (or.inr $ set.ext (λ z, ⟨λ ⟨a,_⟩, a, λ a, ⟨a,a⟩⟩)) end
, U := λ ℱ h₁,
    or.rec_on (classical.em (univ ∈ ℱ))
        (λ h, or.inr $ set.ext $ (λ a, ⟨λ x, ⟨⟩,λ x, ⟨univ,h,x⟩⟩))
        (λ h, or.inl $ set.ext $ (λ a, ⟨λ ⟨U,h₂,h₃⟩, or.rec_on (h₁ h₂) (λ p, p ▸ h₃) (λ p, absurd h₂ (eq.symm p ▸ h) ), λ x, false.rec_on _ x⟩))
}
instance : has_initial (topology X) :=
{  bot := indiscrete X
,  bot_le := λ τ U oU, begin cases oU, rw oU, apply @has_empty, rw oU, apply top  end
}
def discrete (α : Type u) : topology α :=
{ 𝒪 := set.powerset univ
, top := λ _ _, ⟨⟩, i := λ _ _ _ _ _ _, ⟨⟩, U := λ _ _ _ _, ⟨⟩
}
instance : has_terminal (topology X) :=
{   top := discrete X
,   le_top := λ τ U oU u uX, trivial }
def is_lim (ℱ : filter X) (x : X) : Prop := ℱ ≤ nhds x
/-- The topology of neighbourhoods on a filter -/
def free {X : Type u} (x : X) (ℱ : filter X) : topology X :=
{ 𝒪 := {U|x ∈ U → U ∈ ℱ}
, top := λ _, filter.univ_sets _
, i := λ A B fA fB i, filter.inter_sets _ (fA i.1) (fB i.2)
, U := λ 𝒢 f𝒢 ⟨G, GG, xG⟩, filter.sets_of_superset ℱ (f𝒢 GG xG) (λ y o, ⟨G,GG,o⟩)
}
#check order_dual
def is_galois {X : Type u} {x : X} {ℱ : filter X} (τ : topology X) : ℱ ≤ @nhds _ τ x ↔ τ ≤ free x ℱ :=
⟨
    assume l : ∀ U, U ∈ @nhds _ τ x → U ∈ ℱ,
    assume U (oU : U ∈ @𝒪 _ τ) (xU : x ∈ U),
    l U $ free_gen.basic ⟨principal U, ⟨U,⟨xU,oU⟩,rfl⟩, λ _, id⟩
,   assume r : ∀ V, V ∈ @𝒪 X τ → x ∈ V → V ∈ ℱ,
    -- λ U nU, free.is_galois.mp (λ V ⟨f,⟨W,⟨h₄,h₆⟩,h₅⟩,h₃⟩, filter.sets_of_superset ℱ (r h₆ h₄) (-h₅ ▸ h₃)) nU
    assume U nU, begin apply free.is_galois.mp _ nU, assume V h,
    cases h with f h₂, cases h₂ with h₂ h₃, cases h₂ with W h₄, cases h₄ with h₄ h₅,  cases h₄ with h₄ h₆,
    rw <- h₅ at h₃, clear h₅ f, apply filter.sets_of_superset ℱ, apply r, apply h₆, apply h₄, apply h₃,  end
⟩

def continuous {X Y : Type u} [topology X] [topology Y] (f : X → Y) := ∀ V ∈ 𝒪(Y), {x|f(x)∈V} ∈ 𝒪(X)
lemma continuous.id : continuous (id : X → X) := λ V oV, oV
lemma continuous.comp {X Y Z : Type u} [topology X] [topology Y] [topology Z]
    {f : X → Y} {g : Y → Z} (cf : continuous f) (cg : continuous g) : continuous (g ∘ f)
:= λ V oV, cf _ (cg V oV)

def preimage {α : Type u} {β : Type v} (f : α → β) (s : set β) : set α := {x | f x ∈ s}

def induced {X Y : Type u} [topology Y] (f : X → Y) : topology X :=
{ 𝒪 := {U | ∃ V, V ∈ 𝒪(Y) ∧ {x|f(x)∈V} = U}
, top := ⟨univ,top Y,set.ext $ λ a, ⟨λ x, ⟨⟩,λ h, ⟨⟩⟩⟩
, U := λ ℱ,
    assume oℱ : ∀ U ∈ ℱ, ∃ (V : set Y), V ∈ 𝒪 Y ∧ {x : X | f x ∈ V} = U,
    let 𝒢 := {S ∈ 𝒪(Y)| ∃ (U ∈ ℱ), U = {x|f x ∈ S}} in
    let G := ⋃₀ 𝒢 in
    ⟨G, topology.U 𝒢 (λ S gS, gS.1)
    , set.ext $ λ x,
        ⟨ begin
            assume h : f x ∈ G,
            show x ∈ ⋃₀ ℱ,
            cases h with V h, cases h with h₁ h₂, cases h₁ with h₃ h₄, cases h₄ with U h₅, cases h₅ with h₅ h₆,
            existsi U, existsi h₅, rw h₆, exact h₂
          end
        , begin
            assume h : x ∈ ⋃₀ ℱ,
            show f x ∈ G,
            cases h with U h, cases h with h₁ h₂,
            have h₃ : ∃ (V : set Y), V ∈ 𝒪 Y ∧ {x : X | f x ∈ V} = U, from oℱ U h₁,
            cases h₃ with V h₃, cases h₃ with h₃ h₄,
            existsi V, split, split, exact h₃, simp, existsi U, existsi h₁, exact h₄,
            rw <-h₄ at h₂, exact h₂
        end
        ⟩
    ⟩
, i := λ U V ⟨U',oU',pU'⟩ ⟨V',oV',pV'⟩,
    ⟨U'∩V',topology.i _ _ oU' oV',
    set.ext $ λ x, ⟨λ h,
        begin rw [<-pU',<-pV'], split, exact h.1, exact h.2 end, λ h,
        begin rw [<-pU',<-pV'] at h, exact h end⟩⟩
}

end topology


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

! This file was ported from Lean 3 source module data.multiset.range
! leanprover-community/mathlib commit f2f413b9d4be3a02840d0663dace76e8fe3da053
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Multiset.Basic
import Mathbin.Data.List.Range

/-! # `multiset.range n` gives `{0, 1, ..., n-1}` as a multiset.

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

open List Nat

namespace Multiset

#print Multiset.range /-
-- range
/-- `range n` is the multiset lifted from the list `range n`,
  that is, the set `{0, 1, ..., n-1}`. -/
def range (n : ℕ) : Multiset ℕ :=
  range n
#align multiset.range Multiset.range
-/

#print Multiset.coe_range /-
theorem coe_range (n : ℕ) : ↑(List.range n) = range n :=
  rfl
#align multiset.coe_range Multiset.coe_range
-/

#print Multiset.range_zero /-
@[simp]
theorem range_zero : range 0 = 0 :=
  rfl
#align multiset.range_zero Multiset.range_zero
-/

#print Multiset.range_succ /-
@[simp]
theorem range_succ (n : ℕ) : range (succ n) = n ::ₘ range n := by
  rw [range, range_succ, ← coe_add, add_comm] <;> rfl
#align multiset.range_succ Multiset.range_succ
-/

/- warning: multiset.card_range -> Multiset.card_range is a dubious translation:
lean 3 declaration is
  forall (n : Nat), Eq.{1} Nat (coeFn.{1, 1} (AddMonoidHom.{0, 0} (Multiset.{0} Nat) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} Nat) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} Nat) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} Nat) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} Nat) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} Nat) (Multiset.orderedCancelAddCommMonoid.{0} Nat)))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (fun (_x : AddMonoidHom.{0, 0} (Multiset.{0} Nat) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} Nat) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} Nat) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} Nat) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} Nat) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} Nat) (Multiset.orderedCancelAddCommMonoid.{0} Nat)))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) => (Multiset.{0} Nat) -> Nat) (AddMonoidHom.hasCoeToFun.{0, 0} (Multiset.{0} Nat) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} Nat) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} Nat) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} Nat) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} Nat) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} Nat) (Multiset.orderedCancelAddCommMonoid.{0} Nat)))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.card.{0} Nat) (Multiset.range n)) n
but is expected to have type
  forall (n : Nat), Eq.{1} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : Multiset.{0} Nat) => Nat) (Multiset.range n)) (FunLike.coe.{1, 1, 1} (AddMonoidHom.{0, 0} (Multiset.{0} Nat) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} Nat) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} Nat) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} Nat) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} Nat) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} Nat) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} Nat)))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{0} Nat) (fun (_x : Multiset.{0} Nat) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : Multiset.{0} Nat) => Nat) _x) (AddHomClass.toFunLike.{0, 0, 0} (AddMonoidHom.{0, 0} (Multiset.{0} Nat) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} Nat) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} Nat) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} Nat) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} Nat) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} Nat) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} Nat)))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{0} Nat) Nat (AddZeroClass.toAdd.{0} (Multiset.{0} Nat) (AddMonoid.toAddZeroClass.{0} (Multiset.{0} Nat) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} Nat) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} Nat) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} Nat) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} Nat) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} Nat))))))) (AddZeroClass.toAdd.{0} Nat (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (AddMonoidHomClass.toAddHomClass.{0, 0, 0} (AddMonoidHom.{0, 0} (Multiset.{0} Nat) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} Nat) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} Nat) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} Nat) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} Nat) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} Nat) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} Nat)))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{0} Nat) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} Nat) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} Nat) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} Nat) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} Nat) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} Nat) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} Nat)))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid) (AddMonoidHom.addMonoidHomClass.{0, 0} (Multiset.{0} Nat) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} Nat) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} Nat) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} Nat) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} Nat) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} Nat) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} Nat)))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)))) (Multiset.card.{0} Nat) (Multiset.range n)) n
Case conversion may be inaccurate. Consider using '#align multiset.card_range Multiset.card_rangeₓ'. -/
@[simp]
theorem card_range (n : ℕ) : card (range n) = n :=
  length_range _
#align multiset.card_range Multiset.card_range

#print Multiset.range_subset /-
theorem range_subset {m n : ℕ} : range m ⊆ range n ↔ m ≤ n :=
  range_subset
#align multiset.range_subset Multiset.range_subset
-/

#print Multiset.mem_range /-
@[simp]
theorem mem_range {m n : ℕ} : m ∈ range n ↔ m < n :=
  mem_range
#align multiset.mem_range Multiset.mem_range
-/

#print Multiset.not_mem_range_self /-
@[simp]
theorem not_mem_range_self {n : ℕ} : n ∉ range n :=
  not_mem_range_self
#align multiset.not_mem_range_self Multiset.not_mem_range_self
-/

#print Multiset.self_mem_range_succ /-
theorem self_mem_range_succ (n : ℕ) : n ∈ range (n + 1) :=
  List.self_mem_range_succ n
#align multiset.self_mem_range_succ Multiset.self_mem_range_succ
-/

#print Multiset.range_add /-
theorem range_add (a b : ℕ) : range (a + b) = range a + (range b).map fun x => a + x :=
  congr_arg coe (List.range_add _ _)
#align multiset.range_add Multiset.range_add
-/

#print Multiset.range_disjoint_map_add /-
theorem range_disjoint_map_add (a : ℕ) (m : Multiset ℕ) :
    (range a).Disjoint (m.map fun x => a + x) :=
  by
  intro x hxa hxb
  rw [range, mem_coe, List.mem_range] at hxa
  obtain ⟨c, _, rfl⟩ := mem_map.1 hxb
  exact (self_le_add_right _ _).not_lt hxa
#align multiset.range_disjoint_map_add Multiset.range_disjoint_map_add
-/

/- warning: multiset.range_add_eq_union -> Multiset.range_add_eq_union is a dubious translation:
lean 3 declaration is
  forall (a : Nat) (b : Nat), Eq.{1} (Multiset.{0} Nat) (Multiset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) a b)) (Union.union.{0} (Multiset.{0} Nat) (Multiset.hasUnion.{0} Nat (fun (a : Nat) (b : Nat) => Nat.decidableEq a b)) (Multiset.range a) (Multiset.map.{0, 0} Nat Nat (fun (x : Nat) => HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) a x) (Multiset.range b)))
but is expected to have type
  forall (a : Nat) (b : Nat), Eq.{1} (Multiset.{0} Nat) (Multiset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) a b)) (Union.union.{0} (Multiset.{0} Nat) (Multiset.instUnionMultiset.{0} Nat (fun (a : Nat) (b : Nat) => instDecidableEqNat a b)) (Multiset.range a) (Multiset.map.{0, 0} Nat Nat (fun (x : Nat) => HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) a x) (Multiset.range b)))
Case conversion may be inaccurate. Consider using '#align multiset.range_add_eq_union Multiset.range_add_eq_unionₓ'. -/
theorem range_add_eq_union (a b : ℕ) : range (a + b) = range a ∪ (range b).map fun x => a + x :=
  by
  rw [range_add, add_eq_union_iff_disjoint]
  apply range_disjoint_map_add
#align multiset.range_add_eq_union Multiset.range_add_eq_union

end Multiset