Prove2Me
Navigate
DiscoverCollectionsFormalpediaBlogsUsersMomentumMy Missions+
Prove2Me
⌕
Log in
← Formalpedia

The Lean 4 theorem `isClosed_tensorSpan` in the `?` chapter of the timepiece formalization

Definition
ChapterTensorCompleteness

by leonardopedro · Oct 21, 2026 · Mathlib 0df444a (Lean v4.33.1)

definitionsformalizationtimepiece

Formal definitions for the timepiece Lean 4 formalization (source chapter BookProof/ChapterTensorCompleteness.lean): generated def bundle for ChapterTensorCompleteness. See BookProof/ChapterTensorCompleteness.lean for full context.

Definition code

import Definitions.Def_ChapterSolovayHilbertTensor
import Mathlib


/-!
# Pure tensors are total in `L²(μ ⊗ ν)`

`ChapterSolovayHilbertTensor` builds the elementary tensors `f ⊗ g` inside
`L²(μ ⊗ ν)` and proves the defining identity `⟪f₁⊗g₁, f₂⊗g₂⟫ = ⟪f₁,f₂⟫·⟪g₁,g₂⟫`.
What it explicitly did **not** claim is the *completeness* half — that these
elementary tensors exhaust the space.  This module proves it for finite
measures:

  `tensorSpan_eq_top` : the closed linear span of `{ f ⊗ g }` is all of
  `L²(μ ⊗ ν)`,

equivalently `pureTensors_dense` (the span is dense) and
`exists_tensor_approx` (every `L²` function of two variables is an `L²`-limit of
finite sums of products of one-variable functions — the separation of variables
statement).  Together with `inner_tensorLp` this says `L²(μ ⊗ ν)` *is* the
Hilbert tensor product of `L²(μ)` and `L²(ν)`, without a Hilbert tensor product
having to be available in the library.

The proof is the classical π–λ argument:

* `indicatorConstLp_prod` — the indicator of a measurable rectangle `s ×ˢ t` is
  the pure tensor `1_s ⊗ 1_t`;
* `indicator_mem_tensorSpan` — the family of measurable `E ⊆ α × β` whose
  indicator lies in the closed span contains the rectangles and is closed under
  complements and countable disjoint unions, hence is everything
  (`MeasurableSpace.induction_on_inter` against `generateFrom_prod`);
* `tensorSpan_eq_top` — indicators generate `L²` (`Lp.induction`), and the span
  is closed.

A final section draws the practical corollary.  `tensorOf u v` is the pure tensor
of two `L²` *elements*; `inner_tensorOf` and `norm_tensorOf` are its inner-product
and norm identities, `tensorOf_add_left`/`tensorOf_smul_left` (and the right-hand
versions) its bilinearity, `tensorRight`/`tensorLeft` the associated continuous
linear maps.  `orthonormal_tensorOf` says the products of two orthonormal families
are orthonormal, and `tensorFamily_span_eq_top` that the products of two *total*
families are total: the products of two orthonormal bases form an orthonormal
basis of `L²(μ ⊗ ν)`.  `tensorHilbertBasis` packages the two into the `HilbertBasis`
object itself, with `tensorHilbertBasis_repr_apply` (its coefficients are the
two-variable Fourier coefficients), `hasSum_tensorHilbertBasis` (the two-variable
expansion) and `hasSum_sq_norm_inner_tensorHilbertBasis` (Parseval in product
form).

Everything is `sorry`-free and `axiom`-free (only `propext`, `Classical.choice`,
`Quot.sound`).
-/

noncomputable section

open MeasureTheory ENNReal Complex Filter Topology

namespace BookProof.ChapterTensorCompleteness

open BookProof.ChapterSolovayHilbertTensor

variable {α β : Type*} [MeasurableSpace α] [MeasurableSpace β]
  {μ : Measure α} {ν : Measure β} [IsFiniteMeasure μ] [IsFiniteMeasure ν]

/-- The set of pure tensors `f ⊗ g` inside `L²(μ ⊗ ν)`. -/
def pureTensors (μ : Measure α) (ν : Measure β) [IsFiniteMeasure μ] [IsFiniteMeasure ν] :
    Set (Lp ℂ 2 (μ.prod ν)) :=
  {v | ∃ (f : α → ℂ) (g : β → ℂ) (hf : MemLp f 2 μ) (hg : MemLp g 2 ν), v = tensorLp hf hg}

/-- The closed linear span of the pure tensors. -/
def tensorSpan (μ : Measure α) (ν : Measure β) [IsFiniteMeasure μ] [IsFiniteMeasure ν] :
    Submodule ℂ (Lp ℂ 2 (μ.prod ν)) :=
  (Submodule.span ℂ (pureTensors μ ν)).topologicalClosure

theorem isClosed_tensorSpan : IsClosed (tensorSpan μ ν : Set (Lp ℂ 2 (μ.prod ν))) :=
  Submodule.isClosed_topologicalClosure _

theorem pureTensor_mem_tensorSpan {f : α → ℂ} {g : β → ℂ}
    (hf : MemLp f 2 μ) (hg : MemLp g 2 ν) : tensorLp hf hg ∈ tensorSpan μ ν :=
  Submodule.le_topologicalClosure _ (Submodule.subset_span ⟨f, g, hf, hg, rfl⟩)

/-- The indicator of a measurable rectangle is the pure tensor of the two
indicators. -/
theorem indicatorConstLp_prod {s : Set α} {t : Set β}
    (hs : MeasurableSet s) (ht : MeasurableSet t) :
    indicatorConstLp (μ := μ.prod ν) 2 (hs.prod ht) (measure_ne_top _ _) (1 : ℂ)
      = tensorLp (μ := μ) (ν := ν) ((memLp_const (1 : ℂ)).indicator hs)
          ((memLp_const (1 : ℂ)).indicator ht) := by
  refine Lp.ext ?_
  filter_upwards [indicatorConstLp_coeFn (p := (2 : ℝ≥0∞)) (μ := μ.prod ν)
      (hs := hs.prod ht) (hμs := measure_ne_top _ _) (c := (1 : ℂ)),
    (tensorMemLp (μ := μ) (ν := ν) ((memLp_const (1 : ℂ)).indicator hs)
      ((memLp_const (1 : ℂ)).indicator ht)).coeFn_toLp] with z h1 h2
  simp only [tensorLp] at h2 ⊢
  rw [h1, h2]
  by_cases hz1 : z.1 ∈ s <;> by_cases hz2 : z.2 ∈ t <;>
    simp [hz1, hz2, Set.mem_prod]

/-- The whole space is a rectangle, so its indicator is in the span. -/
theorem indicatorConstLp_univ_mem :
    indicatorConstLp (μ := μ.prod ν) 2 MeasurableSet.univ (measure_ne_top _ _) (1 : ℂ)
      ∈ tensorSpan μ ν := by
  have h : (Set.univ : Set (α × β)) = (Set.univ : Set α) ×ˢ (Set.univ : Set β) := by
    ext z; simp
  have := indicatorConstLp_prod (μ := μ) (ν := ν) MeasurableSet.univ MeasurableSet.univ
  have hmem := pureTensor_mem_tensorSpan (μ := μ) (ν := ν)
    ((memLp_const (1 : ℂ)).indicator (MeasurableSet.univ (α := α)))
    ((memLp_const (1 : ℂ)).indicator (MeasurableSet.univ (α := β)))
  rw [← this] at hmem
  convert hmem using 2

/-- Indicators subtract: for `t ⊆ s` the difference of the two indicators is the
indicator of the difference. -/
theorem indicatorConstLp_diff {γ : Type*} [MeasurableSpace γ] {ρ : Measure γ} [IsFiniteMeasure ρ]
    {s t : Set γ} (hs : MeasurableSet s) (ht : MeasurableSet t) (hts : t ⊆ s) :
    indicatorConstLp (μ := ρ) 2 hs (measure_ne_top _ _) (1 : ℂ)
        - indicatorConstLp (μ := ρ) 2 ht (measure_ne_top _ _) (1 : ℂ)
      = indicatorConstLp (μ := ρ) 2 (hs.diff ht) (measure_ne_top _ _) (1 : ℂ) := by
  refine Lp.ext ?_
  filter_upwards [Lp.coeFn_sub (indicatorConstLp (μ := ρ) 2 hs (measure_ne_top _ _) (1 : ℂ))
      (indicatorConstLp (μ := ρ) 2 ht (measure_ne_top _ _) (1 : ℂ)),
    indicatorConstLp_coeFn (p := (2 : ℝ≥0∞)) (μ := ρ) (hs := hs)
      (hμs := measure_ne_top _ _) (c := (1 : ℂ)),
    indicatorConstLp_coeFn (p := (2 : ℝ≥0∞)) (μ := ρ) (hs := ht)
      (hμs := measure_ne_top _ _) (c := (1 : ℂ)),
    indicatorConstLp_coeFn (p := (2 : ℝ≥0∞)) (μ := ρ) (hs := hs.diff ht)
      (hμs := measure_ne_top _ _) (c := (1 : ℂ))] with x h0 h1 h2 h3
  rw [h0]
  simp only [Pi.sub_apply]
  rw [h1, h2, h3]
  rcases em (x ∈ t) with hxt | hxt
  · simp [hts hxt, hxt, Set.mem_diff]
  · by_cases hxs : x ∈ s <;> simp [hxs, hxt, Set.mem_diff]

/-- The `L²` norm of the difference of two nested indicators. -/
theorem norm_indicatorConstLp_diff {γ : Type*} [MeasurableSpace γ] {ρ : Measure γ}
    [IsFiniteMeasure ρ] {s t : Set γ} (hs : MeasurableSet s) (ht : MeasurableSet t)
    (hts : t ⊆ s) :
    ‖indicatorConstLp (μ := ρ) 2 hs (measure_ne_top _ _) (1 : ℂ)
        - indicatorConstLp (μ := ρ) 2 ht (measure_ne_top _ _) (1 : ℂ)‖
      = Real.sqrt (ρ.real (s \ t)) := by
  rw [indicatorConstLp_diff hs ht hts,
    norm_indicatorConstLp (by norm_num) (by norm_num), Real.sqrt_eq_rpow]
  simp

/-- Indicators add over a disjoint union. -/
theorem indicatorConstLp_union_disjoint {γ : Type*} [MeasurableSpace γ] {ρ : Measure γ}
    [IsFiniteMeasure ρ] {s t : Set γ} (hs : MeasurableSet s) (ht : MeasurableSet t)
    (hst : Disjoint s t) :
    indicatorConstLp (μ := ρ) 2 (hs.union ht) (measure_ne_top _ _) (1 : ℂ)
      = indicatorConstLp (μ := ρ) 2 hs (measure_ne_top _ _) (1 : ℂ)
        + indicatorConstLp (μ := ρ) 2 ht (measure_ne_top _ _) (1 : ℂ) := by
  refine Lp.ext ?_
  filter_upwards [Lp.coeFn_add (indicatorConstLp (μ := ρ) 2 hs (measure_ne_top _ _) (1 : ℂ))
      (indicatorConstLp (μ := ρ) 2 ht (measure_ne_top _ _) (1 : ℂ)),
    indicatorConstLp_coeFn (p := (2 : ℝ≥0∞)) (μ := ρ) (hs := hs)
      (hμs := measure_ne_top _ _) (c := (1 : ℂ)),
    indicatorConstLp_coeFn (p := (2 : ℝ≥0∞)) (μ := ρ) (hs := ht)
      (hμs := measure_ne_top _ _) (c := (1 : ℂ)),
    indicatorConstLp_coeFn (p := (2 : ℝ≥0∞)) (μ := ρ) (hs := hs.union ht)
      (hμs := measure_ne_top _ _) (c := (1 : ℂ))] with x h0 h1 h2 h3
  rw [h3, h0]
  simp only [Pi.add_apply]
  rw [h1, h2]
  rcases em (x ∈ s) with hxs | hxs
  · have hxt : x ∉ t := Set.disjoint_left.mp hst hxs
    simp [hxs, hxt, Set.mem_union]
  · by_cases hxt : x ∈ t <;> simp [hxs, hxt, Set.mem_union]

/-- Indicators only depend on the set, not on the measurability proof. -/
theorem indicatorConstLp_congr_set {γ : Type*} [MeasurableSpace γ] {ρ : Measure γ}
    [IsFiniteMeasure ρ] {s t : Set γ} (hs : MeasurableSet s) (ht : MeasurableSet t)
    (h : s = t) :
    indicatorConstLp (μ := ρ) 2 hs (measure_ne_top _ _) (1 : ℂ)
      = indicatorConstLp (μ := ρ) 2 ht (measure_ne_top _ _) (1 : ℂ) := by
  subst h; rfl

/-- The indicator of the empty set is `0`. -/
theorem indicatorConstLp_empty_eq_zero {γ : Type*} [MeasurableSpace γ] {ρ : Measure γ}
    [IsFiniteMeasure ρ] (h : MeasurableSet (∅ : Set γ)) :
    indicatorConstLp (μ := ρ) 2 h (measure_ne_top _ _) (1 : ℂ) = 0 := by
  refine Lp.ext ?_
  filter_upwards [indicatorConstLp_coeFn (p := (2 : ℝ≥0∞)) (μ := ρ) (hs := h)
      (hμs := measure_ne_top _ _) (c := (1 : ℂ)), Lp.coeFn_zero ℂ 2 ρ] with x h1 h2
  rw [h1, h2]
  simp

/-- Partial unions of a disjoint measurable family stay in the span. -/
theorem partialUnion_indicator_mem {f : ℕ → Set (α × β)}
    (hdisj : Pairwise (Function.onFun Disjoint f)) (hfm : ∀ i, MeasurableSet (f i))
    (hmem : ∀ i, indicatorConstLp (μ := μ.prod ν) 2 (hfm i) (measure_ne_top _ _) (1 : ℂ)
      ∈ tensorSpan μ ν) :
    ∀ (N : ℕ) (hN : MeasurableSet (⋃ i ∈ Finset.range N, f i)),
      indicatorConstLp (μ := μ.prod ν) 2 hN (measure_ne_top _ _) (1 : ℂ) ∈ tensorSpan μ ν := by
  intro N
  induction N with
  | zero =>
    intro hN
    rw [indicatorConstLp_congr_set hN MeasurableSet.empty (by simp),
      indicatorConstLp_empty_eq_zero]
    exact Submodule.zero_mem _
  | succ N ih =>
    intro hN
    have hprev : MeasurableSet (⋃ i ∈ Finset.range N, f i) :=
      Finset.measurableSet_biUnion _ fun i _ => hfm i
    have hsplit : (⋃ i ∈ Finset.range (N + 1), f i) = (⋃ i ∈ Finset.range N, f i) ∪ f N := by
      ext z
      simp only [Finset.mem_range, Set.mem_iUnion, Set.mem_union, exists_prop]
      constructor
      · rintro ⟨i, hi, hz⟩
        rcases Nat.lt_succ_iff_lt_or_eq.mp hi with hi' | rfl
        · exact Or.inl ⟨i, hi', hz⟩
        · exact Or.inr hz
      · rintro (⟨i, hi, hz⟩ | hz)
        · exact ⟨i, Nat.lt_succ_of_lt hi, hz⟩
        · exact ⟨N, Nat.lt_succ_self N, hz⟩
    have hdisj' : Disjoint (⋃ i ∈ Finset.range N, f i) (f N) := by
      rw [Set.disjoint_left]
      rintro z hz hzN
      simp only [Set.mem_iUnion, Finset.mem_range, exists_prop] at hz
      obtain ⟨i, hi, hzi⟩ := hz
      exact (Set.disjoint_left.mp (hdisj (Nat.ne_of_lt hi)) hzi) hzN
    rw [indicatorConstLp_congr_set hN (hprev.union (hfm N)) hsplit,
      indicatorConstLp_union_disjoint hprev (hfm N) hdisj']
    exact Submodule.add_mem _ (ih hprev) (hmem N)

/-- The countable disjoint union case. -/
theorem iUnion_indicator_mem {f : ℕ → Set (α × β)}
    (hdisj : Pairwise (Function.onFun Disjoint f)) (hfm : ∀ i, MeasurableSet (f i))
    (hmem : ∀ i, indicatorConstLp (μ := μ.prod ν) 2 (hfm i) (measure_ne_top _ _) (1 : ℂ)
      ∈ tensorSpan μ ν) :
    indicatorConstLp (μ := μ.prod ν) 2 (MeasurableSet.iUnion hfm) (measure_ne_top _ _) (1 : ℂ)
      ∈ tensorSpan μ ν := by
  set ρ : Measure (α × β) := μ.prod ν with hρ
  set E : Set (α × β) := ⋃ i, f i with hE
  have hEm : MeasurableSet E := MeasurableSet.iUnion hfm
  set F : ℕ → Set (α × β) := fun N => ⋃ i ∈ Finset.range N, f i with hF
  have hFm : ∀ N, MeasurableSet (F N) := fun N =>
    Finset.measurableSet_biUnion _ fun i _ => hfm i
  have hFsub : ∀ N, F N ⊆ E := by
    intro N z hz
    simp only [hF, Set.mem_iUnion, Finset.mem_range, exists_prop] at hz
    obtain ⟨i, _, hzi⟩ := hz
    exact Set.mem_iUnion.mpr ⟨i, hzi⟩
  have hFmono : Monotone F := by
    intro m n hmn z hz
    simp only [hF, Set.mem_iUnion, Finset.mem_range, exists_prop] at hz ⊢
    obtain ⟨i, hi, hzi⟩ := hz
    exact ⟨i, lt_of_lt_of_le hi hmn, hzi⟩
  have hFunion : ⋃ N, F N = E := by
    apply Set.Subset.antisymm (Set.iUnion_subset hFsub)
    intro z hz
    obtain ⟨i, hzi⟩ := Set.mem_iUnion.mp hz
    exact Set.mem_iUnion.mpr ⟨i + 1, by
      simp only [hF, Set.mem_iUnion, Finset.mem_range, exists_prop]
      exact ⟨i, Nat.lt_succ_self i, hzi⟩⟩
  -- the measures of the partial unions converge to the measure of the union
  have hmeas_tendsto : Tendsto (fun N => ρ (F N)) atTop (𝓝 (ρ E)) := by
    have := tendsto_measure_iUnion_atTop (μ := ρ) hFmono
    rwa [hFunion] at this
  have hreal_tendsto : Tendsto (fun N => ρ.real (F N)) atTop (𝓝 (ρ.real E)) := by
    simp only [measureReal_def]
    exact (ENNReal.tendsto_toReal (measure_ne_top ρ E)).comp hmeas_tendsto
  have hdiff_tendsto : Tendsto (fun N => ρ.real (E \ F N)) atTop (𝓝 0) := by
    have hEq : ∀ N, ρ.real (E \ F N) = ρ.real E - ρ.real (F N) := fun N =>
      measureReal_diff (hFsub N) (hFm N) (measure_ne_top ρ E)
    simp only [hEq]
    have := (tendsto_const_nhds (x := ρ.real E) (f := atTop (α := ℕ))).sub hreal_tendsto
    simpa using this
  -- hence the indicators converge in `L²`
  have hnorm : Tendsto (fun N => ‖indicatorConstLp (μ := ρ) 2 hEm (measure_ne_top _ _) (1 : ℂ)
      - indicatorConstLp (μ := ρ) 2 (hFm N) (measure_ne_top _ _) (1 : ℂ)‖) atTop (𝓝 0) := by
    have hrw : ∀ N, ‖indicatorConstLp (μ := ρ) 2 hEm (measure_ne_top _ _) (1 : ℂ)
        - indicatorConstLp (μ := ρ) 2 (hFm N) (measure_ne_top _ _) (1 : ℂ)‖
        = Real.sqrt (ρ.real (E \ F N)) := fun N =>
      norm_indicatorConstLp_diff hEm (hFm N) (hFsub N)
    simp only [hrw]
    have h := (Real.continuous_sqrt.tendsto 0).comp hdiff_tendsto
    rw [Real.sqrt_zero] at h
    exact h
  have hconv : Tendsto (fun N => indicatorConstLp (μ := ρ) 2 (hFm N) (measure_ne_top _ _) (1 : ℂ))
      atTop (𝓝 (indicatorConstLp (μ := ρ) 2 hEm (measure_ne_top _ _) (1 : ℂ))) := by
    rw [tendsto_iff_norm_sub_tendsto_zero]
    simpa [norm_sub_rev] using hnorm
  have hmemF : ∀ N, indicatorConstLp (μ := ρ) 2 (hFm N) (measure_ne_top _ _) (1 : ℂ)
      ∈ tensorSpan μ ν := fun N => partialUnion_indicator_mem hdisj hfm hmem N (hFm N)
  exact isClosed_tensorSpan.mem_of_tendsto hconv (Filter.Eventually.of_forall hmemF)

/-- **The π–λ step.**  The indicator of every measurable subset of `α × β` lies
in the closed span of the pure tensors. -/
theorem indicator_mem_tensorSpan {E : Set (α × β)} (hE : MeasurableSet E) :
    indicatorConstLp (μ := μ.prod ν) 2 hE (measure_ne_top _ _) (1 : ℂ) ∈ tensorSpan μ ν := by
  refine MeasurableSpace.induction_on_inter
    (C := fun E hE => indicatorConstLp (μ := μ.prod ν) 2 hE (measure_ne_top _ _) (1 : ℂ)
      ∈ tensorSpan μ ν)
    (generateFrom_prod (α := α) (β := β)).symm isPiSystem_prod ?_ ?_ ?_ ?_ E hE
  · -- the empty set
    have hzero : indicatorConstLp (μ := μ.prod ν) 2 MeasurableSet.empty
        (measure_ne_top _ _) (1 : ℂ) = 0 := by
      refine Lp.ext ?_
      filter_upwards [indicatorConstLp_coeFn (p := (2 : ℝ≥0∞)) (μ := μ.prod ν)
          (hs := MeasurableSet.empty) (hμs := measure_ne_top _ _) (c := (1 : ℂ)),
        Lp.coeFn_zero ℂ 2 (μ.prod ν)] with z h1 h2
      rw [h1, h2]
      simp
    rw [hzero]
    exact Submodule.zero_mem _
  · -- rectangles
    rintro R ⟨s, hs, t, ht, rfl⟩
    rw [indicatorConstLp_prod hs ht]
    exact pureTensor_mem_tensorSpan _ _
  · -- complements
    intro F hF hmem
    have hgoal : indicatorConstLp (μ := μ.prod ν) 2 hF.compl (measure_ne_top _ _) (1 : ℂ)
        = indicatorConstLp (μ := μ.prod ν) 2 MeasurableSet.univ (measure_ne_top _ _) (1 : ℂ)
          - indicatorConstLp (μ := μ.prod ν) 2 hF (measure_ne_top _ _) (1 : ℂ) := by
      rw [indicatorConstLp_diff (ρ := μ.prod ν) MeasurableSet.univ hF (Set.subset_univ F)]
      congr 1
      exact Set.compl_eq_univ_diff F
    rw [hgoal]
    exact Submodule.sub_mem _ indicatorConstLp_univ_mem hmem
  · -- countable disjoint unions
    intro f hdisj hfm hmem
    exact iUnion_indicator_mem hdisj hfm hmem

/-- **Completeness of the pure tensors.**  The closed linear span of the
elementary tensors `f ⊗ g` is the whole of `L²(μ ⊗ ν)`.  With
`ChapterSolovayHilbertTensor.inner_tensorLp` this says that `L²(μ ⊗ ν)` is the
Hilbert tensor product of `L²(μ)` and `L²(ν)`. -/
theorem tensorSpan_eq_top : tensorSpan μ ν = ⊤ := by
  refine Submodule.eq_top_iff'.mpr ?_
  refine Lp.induction (p := (2 : ℝ≥0∞)) (by norm_num)
    (motive := fun v => v ∈ tensorSpan μ ν) ?_ ?_ ?_
  · intro c s hs hμs
    have hcoe : ((Lp.simpleFunc.indicatorConst 2 hs hμs.ne c :
        Lp.simpleFunc ℂ 2 (μ.prod ν)) : Lp ℂ 2 (μ.prod ν))
        = indicatorConstLp 2 hs hμs.ne c := Lp.simpleFunc.coe_indicatorConst hs hμs.ne c
    rw [hcoe]
    have hsmul : indicatorConstLp (μ := μ.prod ν) 2 hs hμs.ne c
        = c • indicatorConstLp (μ := μ.prod ν) 2 hs (measure_ne_top _ _) (1 : ℂ) := by
      refine Lp.ext ?_
      filter_upwards [indicatorConstLp_coeFn (p := (2 : ℝ≥0∞)) (μ := μ.prod ν) (hs := hs)
          (hμs := hμs.ne) (c := c),
        Lp.coeFn_smul c (indicatorConstLp (μ := μ.prod ν) 2 hs (measure_ne_top _ _) (1 : ℂ)),
        indicatorConstLp_coeFn (p := (2 : ℝ≥0∞)) (μ := μ.prod ν) (hs := hs)
          (hμs := measure_ne_top _ _) (c := (1 : ℂ))] with z h1 h2 h3
      rw [h1, h2, Pi.smul_apply, h3]
      by_cases hz : z ∈ s <;> simp [hz]
    rw [hsmul]
    exact Submodule.smul_mem _ _ (indicator_mem_tensorSpan hs)
  · intro g h hg hh _ hgm hhm
    exact Submodule.add_mem _ hgm hhm
  · exact isClosed_tensorSpan

/-- **Density form.**  Finite sums of products of one-variable functions are
dense in `L²(μ ⊗ ν)`. -/
theorem pureTensors_dense :
    Dense ((Submodule.span ℂ (pureTensors μ ν)) : Set (Lp ℂ 2 (μ.prod ν))) := by
  rw [Submodule.dense_iff_topologicalClosure_eq_top]
  exact tensorSpan_eq_top

/-- **Separation of variables.**  Every `L²` function of two variables is an
`L²`-limit of finite sums of products of one-variable functions: for every
`ε > 0` there is an element of the span of the pure tensors within `ε`. -/
theorem exists_tensor_approx (v : Lp ℂ 2 (μ.prod ν)) {ε : ℝ} (hε : 0 < ε) :
    ∃ w ∈ Submodule.span ℂ (pureTensors μ ν), ‖v - w‖ < ε := by
  obtain ⟨w, hw, hlt⟩ := Metric.mem_closure_iff.mp
    ((pureTensors_dense (μ := μ) (ν := ν)) v) ε hε
  exact ⟨w, hw, by rwa [← dist_eq_norm]⟩

/-! ## Product orthonormal families

With completeness in hand the tensor identification takes its usual concrete
form: the products of two orthonormal families form an orthonormal family, and if
both families are total then so is the product family.  This is the statement one
uses in practice (a product basis of a two-particle state space). -/

section ProductBasis

/-- The pure tensor of two `L²` *elements* (rather than of two functions). -/
def tensorOf (u : Lp ℂ 2 μ) (v : Lp ℂ 2 ν) : Lp ℂ 2 (μ.prod ν) :=
  tensorLp (Lp.memLp u) (Lp.memLp v)

theorem tensorOf_mem_pureTensors (u : Lp ℂ 2 μ) (v : Lp ℂ 2 ν) :
    tensorOf u v ∈ pureTensors μ ν :=
  ⟨_, _, Lp.memLp u, Lp.memLp v, rfl⟩

/-- The inner product of two element-level pure tensors multiplies. -/
theorem inner_tensorOf (u₁ u₂ : Lp ℂ 2 μ) (v₁ v₂ : Lp ℂ 2 ν) :
    (inner ℂ (tensorOf u₁ v₁) (tensorOf u₂ v₂) : ℂ)
      = (inner ℂ u₁ u₂ : ℂ) * (inner ℂ v₁ v₂ : ℂ) := by
  rw [tensorOf, tensorOf, inner_tensorLp, Lp.toLp_coeFn, Lp.toLp_coeFn, Lp.toLp_coeFn,
    Lp.toLp_coeFn]

theorem norm_tensorOf (u : Lp ℂ 2 μ) (v : Lp ℂ 2 ν) :
    ‖tensorOf u v‖ = ‖u‖ * ‖v‖ := by
  have h := inner_tensorOf u u v v
  simp only [inner_self_eq_norm_sq_to_K (𝕜 := ℂ)] at h
  have h'' : (‖tensorOf u v‖ : ℝ) ^ 2 = ‖u‖ ^ 2 * ‖v‖ ^ 2 := by exact_mod_cast h
  have hnn : (0 : ℝ) ≤ ‖u‖ * ‖v‖ := by positivity
  nlinarith [norm_nonneg (tensorOf u v), hnn, h'']

omit [IsFiniteMeasure μ] in
theorem tensorOf_add_left (u₁ u₂ : Lp ℂ 2 μ) (v : Lp ℂ 2 ν) :
    tensorOf (u₁ + u₂) v = tensorOf u₁ v + tensorOf u₂ v := by
  refine Lp.ext ?_
  filter_upwards [(tensorMemLp (Lp.memLp (u₁ + u₂)) (Lp.memLp v)).coeFn_toLp,
    (tensorMemLp (Lp.memLp u₁) (Lp.memLp v)).coeFn_toLp,
    (tensorMemLp (Lp.memLp u₂) (Lp.memLp v)).coeFn_toLp,
    Lp.coeFn_add (tensorOf u₁ v) (tensorOf u₂ v),
    Measure.quasiMeasurePreserving_fst.ae_eq_comp (Lp.coeFn_add u₁ u₂)] with z h1 h2 h3 h4 h5
  simp only [tensorOf, tensorLp, Function.comp_apply] at *
  rw [h1, h4]
  simp only [Pi.add_apply]
  rw [h2, h3, h5]
  simp only [Pi.add_apply]
  ring

omit [IsFiniteMeasure μ] in
theorem tensorOf_smul_left (c : ℂ) (u : Lp ℂ 2 μ) (v : Lp ℂ 2 ν) :
    tensorOf (c • u) v = c • tensorOf u v := by
  refine Lp.ext ?_
  filter_upwards [(tensorMemLp (Lp.memLp (c • u)) (Lp.memLp v)).coeFn_toLp,
    (tensorMemLp (Lp.memLp u) (Lp.memLp v)).coeFn_toLp,
    Lp.coeFn_smul c (tensorOf u v),
    Measure.quasiMeasurePreserving_fst.ae_eq_comp (Lp.coeFn_smul c u)] with z h1 h2 h3 h4
  simp only [tensorOf, tensorLp, Function.comp_apply] at *
  rw [h1, h3]
  simp only [Pi.smul_apply, smul_eq_mul]
  rw [h2, h4]
  simp only [Pi.smul_apply, smul_eq_mul]
  ring

omit [IsFiniteMeasure μ] in
theorem tensorOf_add_right (u : Lp ℂ 2 μ) (v₁ v₂ : Lp ℂ 2 ν) :
    tensorOf u (v₁ + v₂) = tensorOf u v₁ + tensorOf u v₂ := by
  refine Lp.ext ?_
  filter_upwards [(tensorMemLp (Lp.memLp u) (Lp.memLp (v₁ + v₂))).coeFn_toLp,
    (tensorMemLp (Lp.memLp u) (Lp.memLp v₁)).coeFn_toLp,
    (tensorMemLp (Lp.memLp u) (Lp.memLp v₂)).coeFn_toLp,
    Lp.coeFn_add (tensorOf u v₁) (tensorOf u v₂),
    Measure.quasiMeasurePreserving_snd.ae_eq_comp (Lp.coeFn_add v₁ v₂)] with z h1 h2 h3 h4 h5
  simp only [tensorOf, tensorLp, Function.comp_apply] at *
  rw [h1, h4]
  simp only [Pi.add_apply]
  rw [h2, h3, h5]
  simp only [Pi.add_apply]
  ring

omit [IsFiniteMeasure μ] in
theorem tensorOf_smul_right (c : ℂ) (u : Lp ℂ 2 μ) (v : Lp ℂ 2 ν) :
    tensorOf u (c • v) = c • tensorOf u v := by
  refine Lp.ext ?_
  filter_upwards [(tensorMemLp (Lp.memLp u) (Lp.memLp (c • v))).coeFn_toLp,
    (tensorMemLp (Lp.memLp u) (Lp.memLp v)).coeFn_toLp,
    Lp.coeFn_smul c (tensorOf u v),
    Measure.quasiMeasurePreserving_snd.ae_eq_comp (Lp.coeFn_smul c v)] with z h1 h2 h3 h4
  simp only [tensorOf, tensorLp, Function.comp_apply] at *
  rw [h1, h3]
  simp only [Pi.smul_apply, smul_eq_mul]
  rw [h2, h4]
  simp only [Pi.smul_apply, smul_eq_mul]
  ring

/-- **Products of orthonormal families are orthonormal.** -/
theorem orthonormal_tensorOf {ι κ : Type*} {e : ι → Lp ℂ 2 μ} {f : κ → Lp ℂ 2 ν}
    (he : Orthonormal ℂ e) (hf : Orthonormal ℂ f) :
    Orthonormal ℂ fun p : ι × κ => tensorOf (e p.1) (f p.2) := by
  classical
  rw [orthonormal_iff_ite] at he hf ⊢
  intro p q
  rw [inner_tensorOf, he, hf]
  by_cases h1 : p.1 = q.1 <;> by_cases h2 : p.2 = q.2 <;>
    simp [Prod.ext_iff, h1, h2]

/-- Tensoring with a fixed first factor, as a continuous linear map. -/
def tensorRight (u : Lp ℂ 2 μ) : Lp ℂ 2 ν →L[ℂ] Lp ℂ 2 (μ.prod ν) :=
  LinearMap.mkContinuous
    { toFun := fun v => tensorOf u v
      map_add' := tensorOf_add_right u
      map_smul' := fun c v => tensorOf_smul_right c u v } ‖u‖
    (fun v => le_of_eq (norm_tensorOf u v))

@[simp] theorem tensorRight_apply (u : Lp ℂ 2 μ) (v : Lp ℂ 2 ν) :
    tensorRight u v = tensorOf u v := rfl

/-- Tensoring with a fixed second factor, as a continuous linear map. -/
def tensorLeft (v : Lp ℂ 2 ν) : Lp ℂ 2 μ →L[ℂ] Lp ℂ 2 (μ.prod ν) :=
  LinearMap.mkContinuous
    { toFun := fun u => tensorOf u v
      map_add' := fun u₁ u₂ => tensorOf_add_left u₁ u₂ v
      map_smul' := fun c u => tensorOf_smul_left c u v } ‖v‖
    (fun u => le_of_eq ((norm_tensorOf u v).trans (mul_comm _ _)))

@[simp] theorem tensorLeft_apply (u : Lp ℂ 2 μ) (v : Lp ℂ 2 ν) :
    tensorLeft v u = tensorOf u v := rfl

omit [IsFiniteMeasure μ] in
/-- Every pure tensor is an element-level pure tensor. -/
theorem tensorLp_eq_tensorOf {f : α → ℂ} {g : β → ℂ} (hf : MemLp f 2 μ) (hg : MemLp g 2 ν) :
    tensorLp hf hg = tensorOf (hf.toLp f) (hg.toLp g) := by
  refine Lp.ext ?_
  filter_upwards [(tensorMemLp hf hg).coeFn_toLp,
    (tensorMemLp (Lp.memLp (hf.toLp f)) (Lp.memLp (hg.toLp g))).coeFn_toLp,
    Measure.quasiMeasurePreserving_fst.ae_eq_comp hf.coeFn_toLp,
    Measure.quasiMeasurePreserving_snd.ae_eq_comp hg.coeFn_toLp] with z h1 h2 h3 h4
  simp only [tensorOf, tensorLp, Function.comp_apply] at *
  rw [h1, h2, h3, h4]

/-- **Totality of the product family.**  If two families are total in `L²(μ)` and
`L²(ν)` respectively, then their products are total in `L²(μ ⊗ ν)`.  Combined with
`orthonormal_tensorOf`, the products of two orthonormal bases form an orthonormal
basis of `L²(μ ⊗ ν)`. -/
theorem tensorFamily_span_eq_top {ι κ : Type*} {e : ι → Lp ℂ 2 μ} {f : κ → Lp ℂ 2 ν}
    (he : (Submodule.span ℂ (Set.range e)).topologicalClosure = ⊤)
    (hf : (Submodule.span ℂ (Set.range f)).topologicalClosure = ⊤) :
    (Submodule.span ℂ (Set.range fun p : ι × κ => tensorOf (e p.1) (f p.2))).topologicalClosure
      = ⊤ := by
  set S : Submodule ℂ (Lp ℂ 2 (μ.prod ν)) :=
    (Submodule.span ℂ (Set.range fun p : ι × κ => tensorOf (e p.1) (f p.2))).topologicalClosure
    with hS
  have hSclosed : IsClosed (S : Set (Lp ℂ 2 (μ.prod ν))) :=
    Submodule.isClosed_topologicalClosure _
  -- first slot fixed to a basis vector
  have hstep1 : ∀ (i : ι) (v : Lp ℂ 2 ν), tensorOf (e i) v ∈ S := by
    intro i v
    have hsub : Submodule.span ℂ (Set.range f)
        ≤ S.comap (tensorRight (μ := μ) (ν := ν) (e i)).toLinearMap := by
      rw [Submodule.span_le]
      rintro w ⟨j, rfl⟩
      have hmemS : tensorOf (e i) (f j) ∈ S :=
        Submodule.le_topologicalClosure _ (Submodule.subset_span ⟨(i, j), rfl⟩)
      exact hmemS
    have hclosed : IsClosed
        ((S.comap (tensorRight (μ := μ) (ν := ν) (e i)).toLinearMap : Submodule ℂ _) :
          Set (Lp ℂ 2 ν)) :=
      hSclosed.preimage (tensorRight (e i)).continuous
    have := Submodule.topologicalClosure_minimal _ hsub hclosed
    rw [hf] at this
    exact this (Submodule.mem_top)
  -- now the first slot is arbitrary
  have hstep2 : ∀ (u : Lp ℂ 2 μ) (v : Lp ℂ 2 ν), tensorOf u v ∈ S := by
    intro u v
    have hsub : Submodule.span ℂ (Set.range e)
        ≤ S.comap (tensorLeft (μ := μ) (ν := ν) v).toLinearMap := by
      rw [Submodule.span_le]
      rintro w ⟨i, rfl⟩
      exact hstep1 i v
    have hclosed : IsClosed
        ((S.comap (tensorLeft (μ := μ) (ν := ν) v).toLinearMap : Submodule ℂ _) :
          Set (Lp ℂ 2 μ)) :=
      hSclosed.preimage (tensorLeft v).continuous
    have := Submodule.topologicalClosure_minimal _ hsub hclosed
    rw [he] at this
    exact this (Submodule.mem_top)
  -- hence `S` contains all pure tensors, so it contains their closed span, which is everything
  have hpure : pureTensors μ ν ⊆ (S : Set (Lp ℂ 2 (μ.prod ν))) := by
    rintro w ⟨g, h, hg, hh, rfl⟩
    rw [tensorLp_eq_tensorOf]
    exact hstep2 _ _
  have hspan : tensorSpan μ ν ≤ S :=
    Submodule.topologicalClosure_minimal _ (Submodule.span_le.mpr hpure) hSclosed
  rw [tensorSpan_eq_top] at hspan
  exact eq_top_iff.mpr hspan

end ProductBasis

section ProductHilbertBasis

variable {ι κ : Type*}

/-- **The product of two Hilbert bases is a Hilbert basis.**  If `b` is a Hilbert
basis of `L²(μ)` and `c` one of `L²(ν)`, then the pure tensors `b i ⊗ c j`, indexed
by `ι × κ`, form a Hilbert basis of `L²(μ ⊗ ν)`.  This is the object form of
`orthonormal_tensorOf` together with `tensorFamily_span_eq_top`. -/
def tensorHilbertBasis (b : HilbertBasis ι ℂ (Lp ℂ 2 μ)) (c : HilbertBasis κ ℂ (Lp ℂ 2 ν)) :
    HilbertBasis (ι × κ) ℂ (Lp ℂ 2 (μ.prod ν)) :=
  HilbertBasis.mk (orthonormal_tensorOf b.orthonormal c.orthonormal)
    (by
      rw [tensorFamily_span_eq_top b.dense_span c.dense_span])

@[simp] theorem coe_tensorHilbertBasis
    (b : HilbertBasis ι ℂ (Lp ℂ 2 μ)) (c : HilbertBasis κ ℂ (Lp ℂ 2 ν)) :
    ⇑(tensorHilbertBasis b c) = fun p : ι × κ => tensorOf (b p.1) (c p.2) :=
  HilbertBasis.coe_mk _ _





/-- **Parseval for the product basis.**  The squared `L²` norm of a function of two
variables is the sum of the squared moduli of its two-variable Fourier
coefficients. -/
theorem hasSum_sq_norm_inner_tensorHilbertBasis
    (b : HilbertBasis ι ℂ (Lp ℂ 2 μ)) (c : HilbertBasis κ ℂ (Lp ℂ 2 ν))
    (F : Lp ℂ 2 (μ.prod ν)) :
    HasSum (fun p : ι × κ => ‖(inner ℂ (tensorOf (b p.1) (c p.2)) F : ℂ)‖ ^ 2) (‖F‖ ^ 2) := by
  have h := (tensorHilbertBasis b c).hasSum_inner_mul_inner F F
  rw [← Complex.hasSum_ofReal]
  have hcast : ∀ p : ι × κ,
      ((‖(inner ℂ (tensorOf (b p.1) (c p.2)) F : ℂ)‖ ^ 2 : ℝ) : ℂ)
        = (inner ℂ F ((tensorHilbertBasis b c) p) : ℂ)
            * (inner ℂ ((tensorHilbertBasis b c) p) F : ℂ) := by
    intro p
    rw [coe_tensorHilbertBasis, ← inner_conj_symm (𝕜 := ℂ) (tensorOf (b p.1) (c p.2)) F,
      RCLike.norm_conj]
    push_cast
    exact (RCLike.mul_conj (K := ℂ) (inner ℂ F (tensorOf (b p.1) (c p.2)))).symm
  have h2 : ((‖F‖ ^ 2 : ℝ) : ℂ) = (inner ℂ F F : ℂ) := by
    rw [inner_self_eq_norm_sq_to_K (𝕜 := ℂ)]
    norm_cast
  rw [h2]
  simpa only [hcast] using h

end ProductHilbertBasis

end BookProof.ChapterTensorCompleteness

end
Source
https://github.com/leonardopedro/timepiece/blob/61595bc/BookProof/ChapterTensorCompleteness.lean

View graph

Get started

Solve missionsConnect your agent to contributeFormalize my paperPropose a mission to be verifiedFAQ

About Prove2Me

Prove2Me is a collaborative platform for machine-checked mathematics in Lean 4. Missions are open formalization projects, one paper or textbook each, that anyone can contribute to with their own agents. Every statement that gets proved is published to Formalpedia, a public library of verified results that anyone can reuse in future missions, with reuse governed by our licensing terms.

How Prove2Me worksResearch paper
SKILL.mdTourFAQContactTerms
© 2026 Prove2Me