From 665cf2b65dada3d388e985c1aeae44404d266d63 Mon Sep 17 00:00:00 2001 From: Fergus Munro <94051987+FergusMunro@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:14:26 +0100 Subject: [PATCH 01/20] Migrating alpha file import to lean + adding testing (#1552) * created alphaFileImports.lean * updated lakefile and agents.md to include alphaFileImports as build target * included alphaFileImports and noAlphaImports in lint_all.lean * changed alphaBuild workflow to use new lean script * modified lean checkers to allow for command line arguments * created Meta/test directory, populated with dummy project violating linter rules, and created testing lean script2 * added testImportScript to lakefile * added text for new checks in lint_all * temporarily removed Meta file * Removed newlines and accidental test case. * removed a few extra newlines --------- Co-authored-by: FergusMunro --- .github/workflows/alphaBuild.yml | 10 ++--- AGENTS.md | 2 +- lakefile.toml | 10 +++++ scripts/PhyslibAlpha/alphaFileImports.lean | 49 ++++++++++++++++++++++ scripts/PhyslibAlpha/noAlphaImports.lean | 13 +++--- scripts/lint_all.lean | 8 ++++ 6 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 scripts/PhyslibAlpha/alphaFileImports.lean diff --git a/.github/workflows/alphaBuild.yml b/.github/workflows/alphaBuild.yml index d3d3552ff1..28e8d64a3f 100644 --- a/.github/workflows/alphaBuild.yml +++ b/.github/workflows/alphaBuild.yml @@ -53,6 +53,10 @@ jobs: - name: Check no PhyslibAlpha in Physlib and QuantumInfo run: env LEAN_ABORT_ON_PANIC=1 lake exe noAlphaImports + - name: Check PhyslibAlpha imports + run: env LEAN_ABORT_ON_PANIC=1 lake exe alphaFileImports + + style_lint: name: Python based linters runs-on: ubuntu-latest @@ -76,12 +80,6 @@ jobs: with: python-version: 3.8 - - name: Check PhyslibAlpha imports - run: | - chmod u+x scripts/PhyslibAlpha/alphaFileImports.py - ./scripts/PhyslibAlpha/alphaFileImports.py - - - name: Python linters for PhyslibAlpha run: | chmod u+x scripts/PhyslibAlpha/alphaPythonLinters.sh diff --git a/AGENTS.md b/AGENTS.md index 5662ffe69f..73bc0d0a48 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,7 +59,7 @@ When a long proof cannot be split, make sure it contains comments. - If edited a `PhyslibAlpha` file, check the following: - `lake exe runPhyslibAlphaLinters` - `lake exe noAlphaImports` - - `./scripts/PhyslibAlpha/alphaFileImports.py` + - `lake exe alphaFileImports` - `./scripts/PhyslibAlpha/alphaPythonLinters.sh` ## PR scope diff --git a/lakefile.toml b/lakefile.toml index 05f6544d90..4f380c0a02 100644 --- a/lakefile.toml +++ b/lakefile.toml @@ -69,6 +69,16 @@ name = "noAlphaImports" srcDir = "scripts/PhyslibAlpha" supportInterpreter = true +[[lean_exe]] +name = "alphaFileImports" +srcDir = "scripts/PhyslibAlpha" +supportInterpreter = true + +[[lean_exe]] +name = "testImportScripts" +srcDir = "Meta/test" +supportInterpreter = true + [[lean_exe]] name = "free_simps" srcDir = "scripts/MetaPrograms" diff --git a/scripts/PhyslibAlpha/alphaFileImports.lean b/scripts/PhyslibAlpha/alphaFileImports.lean new file mode 100644 index 0000000000..28e97b7410 --- /dev/null +++ b/scripts/PhyslibAlpha/alphaFileImports.lean @@ -0,0 +1,49 @@ +import Lean +import Physlib.Meta.AllFilePaths +import Std.Data.HashSet + + +/-! +Copyright (c) 2026 Fergus Munro. All rights reserved. +Released under Apache 2.0 license. +Authors: Fergus Munro +-/ + +open Lean +open Std +open System + +def extractModuleNameFromFilePath (path : FilePath) : String := + ".".intercalate ((path.withExtension "").components.drop 1) + +def extractModuleNameFromImport (importString : String) : String := + let rec findAfterImport : List String → String + | "import" :: x :: _ => x + | _ :: xs => findAfterImport xs + | [] => "" + + findAfterImport ((importString.split Char.isWhitespace).toList.map toString) + +def checkAllFilesImported (directory : String) (mainFilePath : String) : (IO Bool) := do + let modules : HashSet String := HashSet.ofArray $ (← getFilePaths directory).map extractModuleNameFromFilePath + let importedModules := HashSet.ofArray $ ((← IO.FS.lines mainFilePath).filter + (·.contains "import")).map extractModuleNameFromImport + let diff := modules \ importedModules + if diff.size > 0 + then do + IO.println s!"Error: The following .lean files are not imported in {mainFilePath}:" + for module_name in diff do + IO.println s!" - public import {module_name}" + return False + else do + IO.println s!"✓ All {modules.size} .lean files in {directory} are imported in {mainFilePath}" + return True + +unsafe def main (args : List String) : IO Unit := do + let (dir, file) := match args with + | d :: f :: [] => (d, f) + | _ => ("./PhyslibAlpha", "./PhyslibAlpha.lean") + let success ← checkAllFilesImported dir file + if !success then + IO.Process.exit 1 + diff --git a/scripts/PhyslibAlpha/noAlphaImports.lean b/scripts/PhyslibAlpha/noAlphaImports.lean index 39ce265888..bb54c98fb5 100644 --- a/scripts/PhyslibAlpha/noAlphaImports.lean +++ b/scripts/PhyslibAlpha/noAlphaImports.lean @@ -20,10 +20,9 @@ open System PhyslibAlpha files, and False otherwise, printing the offending files and imports to the standard output. -/ -def areNoAlphaImports : IO Bool := do - let mut violations : Array (FilePath × Name) := #[] +def areNoAlphaImports (modules : List String) : IO Bool := do + let mut violations : Array (FilePath × Name) := #[] - let modules : Array String := #["./Physlib", "./QuantumInfo"] for module in modules do let filePaths ← getFilePaths module @@ -48,8 +47,12 @@ def areNoAlphaImports : IO Bool := do IO.println "No violations found. All files passed the check." return True -unsafe def main (_ : List String) : IO Unit := do - let success ← areNoAlphaImports +unsafe def main (args : List String) : IO Unit := do + let dirs := match args with + | [] => ["./Physlib", "./QuantumInfo"] + | _ => args + + let success ← areNoAlphaImports dirs if !success then IO.Process.exit 1 diff --git a/scripts/lint_all.lean b/scripts/lint_all.lean index 47ec83fbde..2ab1d4879d 100644 --- a/scripts/lint_all.lean +++ b/scripts/lint_all.lean @@ -24,6 +24,14 @@ def main (args : List String) : IO UInt32 := do let importCheck ← IO.Process.output {cmd := "lake", args := #["exe", "check_file_imports"]} println! importCheck.stdout + println! "\x1b[36m(3/7) Illegal Imports\x1b[0m" + let noAlphaImports ← IO.Process.output {cmd := "lake", args := #["exe", "noAlphaImports"]} + println! noAlphaImports.stdout + + println! "\x1b[36m(3/7) Ensuring all PhyslibAlpha modules imported\x1b[0m" + let alphaFileImports ← IO.Process.output {cmd := "lake", args := #["exe", "alphaFileImports"]} + println! alphaFileImports.stdout + println! "\x1b[36m(4/7) TODO tag duplicates \x1b[0m" let todoCheck ← IO.Process.output {cmd := "lake", args := #["exe", "check_dup_tags"]} println! todoCheck.stdout From 8edacb9d7ddadb5e1709954eea4b09162ec5bf95 Mon Sep 17 00:00:00 2001 From: Zhi Kai Pong <46996788+zhikaip@users.noreply.github.com> Date: Sat, 22 Aug 2026 07:35:52 +0100 Subject: [PATCH 02/20] chore: remove 53 defeq set_option (#1557) --- .../Distributional/Dynamics/KineticTerm.lean | 1 - Physlib/Mathematics/DataStructures/Matrix/LieTrace.lean | 1 - Physlib/Mathematics/Distribution/PowMul.lean | 1 - Physlib/Mathematics/List.lean | 1 - Physlib/Mathematics/SchurTriangulation.lean | 2 -- Physlib/Mathematics/SpecialFunctions/PhysHermite.lean | 1 - .../RHN/AnomalyCancellation/FamilyMaps.lean | 1 - .../RHN/AnomalyCancellation/Permutations.lean | 1 - .../PerturbationTheory/FieldSpecification/CrAnSection.lean | 2 -- .../WickContraction/InsertAndContractNat.lean | 4 ---- Physlib/QFT/QED/AnomalyCancellation/Even/BasisLinear.lean | 3 --- Physlib/Relativity/Fermions/Weyl/Metric.lean | 6 ------ Physlib/Relativity/PauliMatrices/AsTensor.lean | 1 - Physlib/Relativity/Tensors/Basic.lean | 2 -- Physlib/Relativity/Tensors/ComplexTensor/Metrics/Pre.lean | 2 -- Physlib/Relativity/Tensors/Contraction/Basis.lean | 1 - Physlib/Relativity/Tensors/Contraction/Pure.lean | 2 -- Physlib/Relativity/Tensors/Evaluation.lean | 5 ----- Physlib/Relativity/Tensors/Product.lean | 1 - Physlib/Relativity/Tensors/RealTensor/Metrics/Pre.lean | 2 -- Physlib/Relativity/Tensors/RealTensor/ToComplex.lean | 2 -- Physlib/Relativity/Tensors/RealTensor/Units/Pre.lean | 2 -- Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Basic.lean | 1 - .../Tensors/RealTensor/Vector/Pre/Contraction.lean | 2 -- Physlib/SpaceAndTime/Space/Derivatives/Grad.lean | 1 - Physlib/SpaceAndTime/Space/Norm/Basic.lean | 2 -- Physlib/Units/Examples.lean | 1 - QuantumInfo/ForMathlib/HermitianMat/Proj.lean | 1 - QuantumInfo/ForMathlib/Majorization.lean | 1 - 29 files changed, 53 deletions(-) diff --git a/Physlib/Electromagnetism/Distributional/Dynamics/KineticTerm.lean b/Physlib/Electromagnetism/Distributional/Dynamics/KineticTerm.lean index e47af046fd..1cf702cadf 100644 --- a/Physlib/Electromagnetism/Distributional/Dynamics/KineticTerm.lean +++ b/Physlib/Electromagnetism/Distributional/Dynamics/KineticTerm.lean @@ -174,7 +174,6 @@ lemma gradKineticTerm_sum_inr_eq {d} {𝓕 : FreeSpace} -/ -set_option backward.isDefEq.respectTransparency false in attribute [-simp] Nat.reduceAdd Nat.reduceSucc Fin.isValue in lemma gradKineticTerm_eq_distTensorDeriv {d} {𝓕 : FreeSpace} (A : DistElectromagneticPotential d) (ε : 𝓢(SpaceTime d, ℝ)) (ν : Fin 1 ⊕ Fin d) : diff --git a/Physlib/Mathematics/DataStructures/Matrix/LieTrace.lean b/Physlib/Mathematics/DataStructures/Matrix/LieTrace.lean index 67a256963a..537f4b33c9 100644 --- a/Physlib/Mathematics/DataStructures/Matrix/LieTrace.lean +++ b/Physlib/Mathematics/DataStructures/Matrix/LieTrace.lean @@ -229,7 +229,6 @@ end Matrix namespace NormedSpace -set_option backward.isDefEq.respectTransparency false in lemma exp_map_algebraMap {n : Type*} [Fintype n] [DecidableEq n] (A : Matrix n n ℝ) : (exp A).map (algebraMap ℝ ℂ) = exp (A.map (algebraMap ℝ ℂ)) := by diff --git a/Physlib/Mathematics/Distribution/PowMul.lean b/Physlib/Mathematics/Distribution/PowMul.lean index 2feed10a9f..8a631828e9 100644 --- a/Physlib/Mathematics/Distribution/PowMul.lean +++ b/Physlib/Mathematics/Distribution/PowMul.lean @@ -43,7 +43,6 @@ lemma norm_iteratedFDeriv_ofRealCLM {x} (i : ℕ) : rw [← norm_iteratedFDeriv_fderiv, h, iteratedFDeriv_const_of_ne n.succ_ne_zero] simp -set_option backward.isDefEq.respectTransparency false in /-- The continuous linear map `𝓢(ℝ, 𝕜) →L[𝕜] 𝓢(ℝ, 𝕜)` taking a Schwartz map `η` to `x * η`. -/ def powOneMul : 𝓢(ℝ, 𝕜) →L[𝕜] 𝓢(ℝ, 𝕜) := by diff --git a/Physlib/Mathematics/List.lean b/Physlib/Mathematics/List.lean index a09e5f3b3c..741ef44a0d 100644 --- a/Physlib/Mathematics/List.lean +++ b/Physlib/Mathematics/List.lean @@ -143,7 +143,6 @@ lemma orderedInsertPos_sigma {I : Type} {f : I → Type} simp_all only split <;> simp_all -set_option backward.isDefEq.respectTransparency false in lemma orderedInsert_get_lt {I : Type} (le1 : I → I → Prop) [DecidableRel le1] (r : List I) (r0 : I) (i : ℕ) (hi : i < orderedInsertPos le1 r r0) : diff --git a/Physlib/Mathematics/SchurTriangulation.lean b/Physlib/Mathematics/SchurTriangulation.lean index 3896e66d04..cbb05dade5 100644 --- a/Physlib/Mathematics/SchurTriangulation.lean +++ b/Physlib/Mathematics/SchurTriangulation.lean @@ -65,7 +65,6 @@ end Equiv /-- The type family parameterized by `Bool` is finite if each type variant is finite. -/ instance [M : Fintype m] [N : Fintype n] (b : Bool) : Fintype (cond b m n) := b.rec N M -set_option backward.isDefEq.respectTransparency false in /-- The type family parameterized by `Bool` has decidable equality if each type variant is decidable. -/ instance [DecidableEq m] [DecidableEq n] : DecidableEq (Σ b, cond b m n) @@ -134,7 +133,6 @@ variable [IsAlgClosed 𝕜] set_option maxHeartbeats 800000 in set_option maxRecDepth 2000 in -set_option backward.isDefEq.respectTransparency false in /-- **Don't use this definition directly.** This is the key algorithm behind `Matrix.schur_triangulation`. -/ protected noncomputable def SchurTriangulationAux.of diff --git a/Physlib/Mathematics/SpecialFunctions/PhysHermite.lean b/Physlib/Mathematics/SpecialFunctions/PhysHermite.lean index e7a10046e2..949ea91334 100644 --- a/Physlib/Mathematics/SpecialFunctions/PhysHermite.lean +++ b/Physlib/Mathematics/SpecialFunctions/PhysHermite.lean @@ -431,7 +431,6 @@ lemma physHermite_norm_cons (n : ℕ) (c : ℝ) : rw [physHermite_norm] at h simpa [mul_pow, neg_mul] using h -set_option backward.isDefEq.respectTransparency false in lemma polynomial_mem_physHermite_span_induction (P : Polynomial ℤ) : (n : ℕ) → (hn : P.natDegree = n) → (P : ℝ → ℝ) ∈ Submodule.span ℝ (Set.range (fun n => (physHermite n : ℝ → ℝ))) diff --git a/Physlib/Particles/BeyondTheStandardModel/RHN/AnomalyCancellation/FamilyMaps.lean b/Physlib/Particles/BeyondTheStandardModel/RHN/AnomalyCancellation/FamilyMaps.lean index 3ec4c35aa8..2b79906aac 100644 --- a/Physlib/Particles/BeyondTheStandardModel/RHN/AnomalyCancellation/FamilyMaps.lean +++ b/Physlib/Particles/BeyondTheStandardModel/RHN/AnomalyCancellation/FamilyMaps.lean @@ -36,7 +36,6 @@ def chargesMapOfSpeciesMap {n m : ℕ} (f : (SMνSpecies n).Charges →ₗ[ℚ] rw [map_smul, toSMSpecies_toSpecies_inv, toSMSpecies_toSpecies_inv, map_smul] rfl -set_option backward.isDefEq.respectTransparency false in lemma chargesMapOfSpeciesMap_toSpecies {n m : ℕ} (f : (SMνSpecies n).Charges →ₗ[ℚ] (SMνSpecies m).Charges) (S : (SMνCharges n).Charges) (j : Fin 6) : diff --git a/Physlib/Particles/BeyondTheStandardModel/RHN/AnomalyCancellation/Permutations.lean b/Physlib/Particles/BeyondTheStandardModel/RHN/AnomalyCancellation/Permutations.lean index 326ed223c3..9dc6c157de 100644 --- a/Physlib/Particles/BeyondTheStandardModel/RHN/AnomalyCancellation/Permutations.lean +++ b/Physlib/Particles/BeyondTheStandardModel/RHN/AnomalyCancellation/Permutations.lean @@ -61,7 +61,6 @@ def repCharges {n : ℕ} : Representation ℚ (PermGroup n) (SMνCharges n).Char intro i exact toSMSpecies_toSpecies_inv _ _ -set_option backward.isDefEq.respectTransparency false in lemma repCharges_toSpecies (f : PermGroup n) (S : (SMνCharges n).Charges) (j : Fin 6) : toSpecies j (repCharges f S) = toSpecies j S ∘ f⁻¹ j := toSMSpecies_toSpecies_inv _ _ diff --git a/Physlib/QFT/PerturbationTheory/FieldSpecification/CrAnSection.lean b/Physlib/QFT/PerturbationTheory/FieldSpecification/CrAnSection.lean index 88af2573a9..6f458c7485 100644 --- a/Physlib/QFT/PerturbationTheory/FieldSpecification/CrAnSection.lean +++ b/Physlib/QFT/PerturbationTheory/FieldSpecification/CrAnSection.lean @@ -133,7 +133,6 @@ def singletonEquiv {φ : 𝓕.FieldOp} : CrAnSection [φ] ≃ simp only [head] rfl -set_option backward.isDefEq.respectTransparency false in /-- An equivalence separating the head of a creation and annihilation section from the tail. -/ def consEquiv {φ : 𝓕.FieldOp} {φs : List 𝓕.FieldOp} : CrAnSection (φ :: φs) ≃ @@ -384,7 +383,6 @@ lemma eraseIdxEquiv_apply_snd {n : ℕ} (ψs : CrAnSection φs) (hn : n < φs.le simp only [Nat.succ_eq_add_one, le_add_iff_nonneg_right, zero_le, inf_of_le_left] exact Eq.symm (List.eraseIdx_eq_take_drop_succ ψs.1 n) -set_option backward.isDefEq.respectTransparency false in lemma eraseIdxEquiv_symm_eq_take_cons_drop {n : ℕ} (φs : List 𝓕.FieldOp) (hn : n < φs.length) (a : 𝓕.fieldOpToCrAnType φs[n]) (s : CrAnSection (φs.eraseIdx n)) : (eraseIdxEquiv n φs hn).symm ⟨a, s⟩ = diff --git a/Physlib/QFT/PerturbationTheory/WickContraction/InsertAndContractNat.lean b/Physlib/QFT/PerturbationTheory/WickContraction/InsertAndContractNat.lean index 208fec49d6..6133977cd5 100644 --- a/Physlib/QFT/PerturbationTheory/WickContraction/InsertAndContractNat.lean +++ b/Physlib/QFT/PerturbationTheory/WickContraction/InsertAndContractNat.lean @@ -247,12 +247,10 @@ lemma insertAndContractNat_some_uncontracted (c : WickContraction n) (i : Fin n. -/ -set_option backward.isDefEq.respectTransparency false in lemma insertAndContractNat_none_getDual?_isNone (c : WickContraction n) (i : Fin n.succ) : ((insertAndContractNat c i none).getDual? i).isNone := by simp [Option.isNone_iff_eq_none, getDual?_eq_none_iff_mem_uncontracted] -set_option backward.isDefEq.respectTransparency false in @[simp] lemma insertAndContractNat_none_getDual?_eq_none (c : WickContraction n) (i : Fin n.succ) : (insertAndContractNat c i none).getDual? i = none := by @@ -346,7 +344,6 @@ lemma insertAndContractNat_erase (c : WickContraction n) (i : Fin n.succ) simp [Fin.succAbove_ne] at hi simp [Finset.mapEmbedding_apply, Finset.map_inj, hn] -set_option backward.isDefEq.respectTransparency false in lemma insertAndContractNat_getDualErase (c : WickContraction n) (i : Fin n.succ) (j : Option c.uncontracted) : (insertAndContractNat c i j).getDualErase i = uncontractedCongr (c := c) (c' := (c.insertAndContractNat i j).erase i) (by simp) j := by @@ -502,7 +499,6 @@ lemma insertLiftSome_bijective {c : WickContraction n} (i : Fin n.succ) (j : c.u -/ -set_option backward.isDefEq.respectTransparency false in lemma insertAndContractNat_injective (i : Fin n.succ) : Function.Injective (fun c => insertAndContractNat c i none) := fun _ _ hc => Subtype.ext (by simpa [insertAndContractNat] using Subtype.ext_iff.mp hc) diff --git a/Physlib/QFT/QED/AnomalyCancellation/Even/BasisLinear.lean b/Physlib/QFT/QED/AnomalyCancellation/Even/BasisLinear.lean index f574ee4e9b..b4d1a8c857 100644 --- a/Physlib/QFT/QED/AnomalyCancellation/Even/BasisLinear.lean +++ b/Physlib/QFT/QED/AnomalyCancellation/Even/BasisLinear.lean @@ -483,7 +483,6 @@ lemma basis_on_other {k : Fin n} {j : Fin (2 * n.succ)} (h1 : j ≠ evenShiftFst (h2 : j ≠ evenShiftSnd k) : basisAsCharges k j = 0 := by simp only [basisAsCharges, if_neg h1, if_neg h2] -set_option backward.isDefEq.respectTransparency false in lemma basis_on_evenShiftFst_other {k j : Fin n} (h : k ≠ j) : basisAsCharges k (evenShiftFst j) = 0 := by rw [ne_eq, Fin.ext_iff] at h @@ -522,14 +521,12 @@ lemma basis_on_evenShiftSnd_other {k j : Fin n} (h : k ≠ j) : rw [basis_evenShiftSnd_eq_neg_evenShiftFst, basis_on_evenShiftFst_other h] rfl -set_option backward.isDefEq.respectTransparency false in lemma basis_on_evenShiftZero (j : Fin n) : basisAsCharges j evenShiftZero = 0 := by refine basis_on_other ?_ ?_ <;> simp only [ne_eq, Fin.ext_iff, evenShiftZero, evenShiftFst, evenShiftSnd, Fin.val_cast, Fin.val_castAdd, Fin.val_natAdd, Fin.val_eq_zero] <;> omega -set_option backward.isDefEq.respectTransparency false in lemma basis_on_evenShiftLast (j : Fin n) : basisAsCharges j evenShiftLast = 0 := by refine basis_on_other ?_ ?_ <;> simp only [ne_eq, Fin.ext_iff, evenShiftLast, evenShiftFst, evenShiftSnd, Fin.val_cast, diff --git a/Physlib/Relativity/Fermions/Weyl/Metric.lean b/Physlib/Relativity/Fermions/Weyl/Metric.lean index 6e6ae5df8e..9d7d075f0d 100644 --- a/Physlib/Relativity/Fermions/Weyl/Metric.lean +++ b/Physlib/Relativity/Fermions/Weyl/Metric.lean @@ -73,7 +73,6 @@ lemma metricRaw_comm_star (M : SL(2,ℂ)) : metricRaw * M.1.map star = ((M.1)⁻ def leftMetricVal : LeftHandedWeyl ⊗[ℂ] LeftHandedWeyl := leftLeftToMatrix.symm (- metricRaw) -set_option backward.isDefEq.respectTransparency false in /-- Expansion of `leftMetricVal` into the left basis. -/ lemma leftMetricVal_expand_tmul : leftMetricVal = - LeftHandedWeyl.basis 0 ⊗ₜ[ℂ] LeftHandedWeyl.basis 1 + @@ -122,7 +121,6 @@ lemma leftMetric_apply_one : leftMetric (1 : ℂ) = leftMetricVal := by def dualLeftMetricVal : (DualLeftHandedWeyl ⊗[ℂ] DualLeftHandedWeyl) := dualLeftdualLeftToMatrix.symm metricRaw -set_option backward.isDefEq.respectTransparency false in /-- Expansion of `dualLeftMetricVal` into the left basis. -/ lemma dualLeftMetricVal_expand_tmul : dualLeftMetricVal = DualLeftHandedWeyl.basis 0 ⊗ₜ[ℂ] DualLeftHandedWeyl.basis 1 - @@ -167,7 +165,6 @@ lemma dualLeftMetric_apply_one : dualLeftMetric (1 : ℂ) = dualLeftMetricVal := def rightMetricVal : (RightHandedWeyl ⊗[ℂ] RightHandedWeyl) := rightRightToMatrix.symm (- metricRaw) -set_option backward.isDefEq.respectTransparency false in /-- Expansion of `rightMetricVal` into the left basis. -/ lemma rightMetricVal_expand_tmul : rightMetricVal = - RightHandedWeyl.basis 0 ⊗ₜ[ℂ] RightHandedWeyl.basis 1 + @@ -225,7 +222,6 @@ lemma rightMetric_apply_one : rightMetric (1 : ℂ) = rightMetricVal := by def dualRightMetricVal : DualRightHandedWeyl ⊗[ℂ] DualRightHandedWeyl := dualRightDualRightToMatrix.symm (metricRaw) -set_option backward.isDefEq.respectTransparency false in /-- Expansion of `rightMetricVal` into the left basis. -/ lemma dualRightMetricVal_expand_tmul : dualRightMetricVal = DualRightHandedWeyl.basis 0 ⊗ₜ[ℂ] DualRightHandedWeyl.basis 1 - @@ -282,7 +278,6 @@ lemma dualRightMetric_apply_one : dualRightMetric (1 : ℂ) = dualRightMetricVal -/ -set_option backward.isDefEq.respectTransparency false in lemma leftDualContraction_apply_metric : (TensorProduct.comm ℂ _ _ <| (TensorProduct.lid ℂ _).lTensor _ <| @@ -320,7 +315,6 @@ lemma dualLeftContraction_apply_metric : zero_ne_one, zero_smul, sub_zero, one_ne_zero, zero_sub, sub_neg_eq_add] rw [leftDualLeftUnit_apply_one, leftDualLeftUnitVal_expand_tmul] -set_option backward.isDefEq.respectTransparency false in lemma rightDualContraction_apply_metric : (TensorProduct.comm ℂ _ _ <| (TensorProduct.lid ℂ _).lTensor _ <| diff --git a/Physlib/Relativity/PauliMatrices/AsTensor.lean b/Physlib/Relativity/PauliMatrices/AsTensor.lean index c2d84e1f4f..34ad5cb901 100644 --- a/Physlib/Relativity/PauliMatrices/AsTensor.lean +++ b/Physlib/Relativity/PauliMatrices/AsTensor.lean @@ -66,7 +66,6 @@ lemma leftRightToMatrix_σSA_inr_1_expand : leftRightToMatrix.symm (pauliBasis ( simp [leftRightToMatrix_symm_expand_tmul, pauliBasis, pauliSelfAdjoint, pauliMatrix] module -set_option backward.isDefEq.respectTransparency false in /-- The expansion of the pauli matrix `σ₃` in terms of a basis of tensor product vectors. -/ lemma leftRightToMatrix_σSA_inr_2_expand : leftRightToMatrix.symm (pauliBasis (Sum.inr 2)) = LeftHandedWeyl.basis 0 ⊗ₜ RightHandedWeyl.basis 0 - diff --git a/Physlib/Relativity/Tensors/Basic.lean b/Physlib/Relativity/Tensors/Basic.lean index 2b73a5e67a..ef18f98362 100644 --- a/Physlib/Relativity/Tensors/Basic.lean +++ b/Physlib/Relativity/Tensors/Basic.lean @@ -515,7 +515,6 @@ lemma permT_pure {n m : ℕ} {c : Fin n → C} {c1 : Fin m → C} PiTensorProduct.reindex_tprod, PiTensorProduct.map_tprod] rfl -set_option backward.isDefEq.respectTransparency false in @[simp] lemma Pure.permP_id_self {n : ℕ} {c : Fin n → C} (p : Pure S c) : Pure.permP (id : Fin n → Fin n) (by simp : IsReindexing c c id) p = p := by @@ -567,7 +566,6 @@ lemma permT_congr {n m : ℕ} {c : Fin n → C} {c1 : Fin m → C} subst hmap htensor rfl -set_option backward.isDefEq.respectTransparency false in @[simp] lemma Pure.permP_permP {n m1 m2 : ℕ} {c : Fin n → C} {c1 : Fin m1 → C} {c2 : Fin m2 → C} {σ : Fin m1 → Fin n} {σ2 : Fin m2 → Fin m1} (h : IsReindexing c c1 σ) diff --git a/Physlib/Relativity/Tensors/ComplexTensor/Metrics/Pre.lean b/Physlib/Relativity/Tensors/ComplexTensor/Metrics/Pre.lean index 0be7cec119..b830d64093 100644 --- a/Physlib/Relativity/Tensors/ComplexTensor/Metrics/Pre.lean +++ b/Physlib/Relativity/Tensors/ComplexTensor/Metrics/Pre.lean @@ -27,7 +27,6 @@ namespace Lorentz def contrMetricVal : (ContrℂModule ⊗[ℂ] ContrℂModule) := contrContrToMatrix.symm ((@minkowskiMatrix 3).map ofRealHom) -set_option backward.isDefEq.respectTransparency false in /-- The expansion of `contrMetricVal` into basis vectors. -/ lemma contrMetricVal_expand_tmul : contrMetricVal = complexContrBasis (Sum.inl 0) ⊗ₜ[ℂ] complexContrBasis (Sum.inl 0) @@ -77,7 +76,6 @@ lemma contrMetric_apply_one : contrMetric (1 : ℂ) = contrMetricVal := by def coMetricVal : (CoℂModule ⊗[ℂ] CoℂModule) := coCoToMatrix.symm ((@minkowskiMatrix 3).map ofRealHom) -set_option backward.isDefEq.respectTransparency false in /-- The expansion of `coMetricVal` into basis vectors. -/ lemma coMetricVal_expand_tmul : coMetricVal = complexCoBasis (Sum.inl 0) ⊗ₜ[ℂ] complexCoBasis (Sum.inl 0) diff --git a/Physlib/Relativity/Tensors/Contraction/Basis.lean b/Physlib/Relativity/Tensors/Contraction/Basis.lean index 164dcab2c2..914a3511af 100644 --- a/Physlib/Relativity/Tensors/Contraction/Basis.lean +++ b/Physlib/Relativity/Tensors/Contraction/Basis.lean @@ -29,7 +29,6 @@ namespace Tensor open ComponentIdx -set_option backward.isDefEq.respectTransparency false in lemma Pure.dropPair_basisVector {n : ℕ} {c : Fin (n + 1 + 1) → C} {i j : Fin (n + 1 + 1)} (hij : i ≠ j) (b : ComponentIdx c) : Pure.dropPair i j hij (basisVector c b) = diff --git a/Physlib/Relativity/Tensors/Contraction/Pure.lean b/Physlib/Relativity/Tensors/Contraction/Pure.lean index 8bd7bae9bd..24d7a89cdd 100644 --- a/Physlib/Relativity/Tensors/Contraction/Pure.lean +++ b/Physlib/Relativity/Tensors/Contraction/Pure.lean @@ -349,7 +349,6 @@ noncomputable def contrP {n : ℕ} {c : Fin (n + 1 + 1) → C} S.Tensor (c ∘ succSuccAbove i j) := (p.contrPCoeff i j hij) • (p.dropPair i j hij.1).toTensor -set_option backward.isDefEq.respectTransparency false in @[simp] lemma contrP_update_add {n : ℕ} [inst : DecidableEq (Fin (n + 1 +1))] {c : Fin (n + 1 + 1) → C} (i j m : Fin (n + 1 + 1)) (hij : i ≠ j ∧ S.τ (c i) = c j) @@ -361,7 +360,6 @@ lemma contrP_update_add {n : ℕ} [inst : DecidableEq (Fin (n + 1 +1))] {c : Fin · simp [contrP, add_smul] · simp [contrP] -set_option backward.isDefEq.respectTransparency false in @[simp] lemma contrP_update_smul {n : ℕ} [inst : DecidableEq (Fin (n + 1 +1))] {c : Fin (n + 1 + 1) → C} (i j m : Fin (n + 1 + 1)) (hij : i ≠ j ∧ S.τ (c i) = c j) diff --git a/Physlib/Relativity/Tensors/Evaluation.lean b/Physlib/Relativity/Tensors/Evaluation.lean index 9b15b7ac59..cab793b231 100644 --- a/Physlib/Relativity/Tensors/Evaluation.lean +++ b/Physlib/Relativity/Tensors/Evaluation.lean @@ -72,7 +72,6 @@ lemma evalPCoeff_basisVector (i : Fin (n + 1)) (φ : basisIdx (c i)) (b' : Compo noncomputable def evalP (i : Fin (n + 1)) (φ : basisIdx (c i)) (p : Pure S c) : Tensor S (c ∘ i.succAbove) := evalPCoeff i φ p • (drop p i).toTensor -set_option backward.isDefEq.respectTransparency false in @[simp] lemma evalP_update_add [inst : DecidableEq (Fin (n + 1))] (i j : Fin (n + 1)) (φ : basisIdx (c i)) (p : Pure S c) @@ -84,7 +83,6 @@ lemma evalP_update_add [inst : DecidableEq (Fin (n + 1))] (i j : Fin (n + 1)) · simp [add_smul] · simp -set_option backward.isDefEq.respectTransparency false in @[simp] lemma evalP_update_smul [inst : DecidableEq (Fin (n + 1))] (i j : Fin (n + 1)) (φ : basisIdx (c i)) (p : Pure S c) @@ -190,7 +188,6 @@ lemma evalT_permT {n m : ℕ} {c : Fin (n + 1) → C} {c' : Fin (m + 1) → C} -/ -set_option backward.isDefEq.respectTransparency false in /-- Commutation of two evaluations on a tensor basis vector. -/ lemma evalT_evalT_basis {n : ℕ} {c : Fin (n + 1 + 1) → C} (k1 : Fin (n + 1 + 1)) (k2 : Fin (n + 1)) (φ1 : basisIdx (c k1)) @@ -228,7 +225,6 @@ lemma evalT_evalT_basis {n : ℕ} {c : Fin (n + 1 + 1) → C} simp only [h2, h1, hntr, ↓reduceIte] · simp only [h2, ↓reduceIte, ite_self] -set_option backward.isDefEq.respectTransparency false in /-- Evaluating two tensor indices commutes, up to the canonical reindexing identifying the two possible orders in which the indices are removed. -/ lemma evalT_evalT {n : ℕ} {c : Fin (n + 1 + 1) → C} @@ -410,7 +406,6 @@ lemma eq_sum_evalT_of_single_tensor_basis {c : C} (t : Tensor S ![c]) : · simp [add_smul, Finset.sum_add_distrib] grind -set_option backward.isDefEq.respectTransparency false in /-- Reconstruction of a tensor from the evaluations of its last index: every `t : Tensor S c` is the sum over basis indices `i` of the evaluation `evalT (Fin.last n) i t` tensored with the basis covector `basis ![c (Fin.last n)] (single.symm i)`, with the appended index diff --git a/Physlib/Relativity/Tensors/Product.lean b/Physlib/Relativity/Tensors/Product.lean index f32c5d7ab1..6128625fd4 100644 --- a/Physlib/Relativity/Tensors/Product.lean +++ b/Physlib/Relativity/Tensors/Product.lean @@ -305,7 +305,6 @@ lemma Pure.prodP_permP_right {n n'} {c : Fin n → C} {c' : Fin n' → C} -/ -set_option backward.isDefEq.respectTransparency false in lemma Pure.prodP_assoc {n n1 n2} {c : Fin n → C} {c1 : Fin n1 → C} {c2 : Fin n2 → C} (p : Pure S c) (p1 : Pure S c1) (p2 : Pure S c2) : diff --git a/Physlib/Relativity/Tensors/RealTensor/Metrics/Pre.lean b/Physlib/Relativity/Tensors/RealTensor/Metrics/Pre.lean index d346da31b0..2125831420 100644 --- a/Physlib/Relativity/Tensors/RealTensor/Metrics/Pre.lean +++ b/Physlib/Relativity/Tensors/RealTensor/Metrics/Pre.lean @@ -38,7 +38,6 @@ lemma preContrMetricVal_expand_tmul {d : ℕ} : preContrMetricVal d = simp [Fintype.sum_sum_type, minkowskiMatrix.inl_0_inl_0, minkowskiMatrix.inr_i_inr_i, sub_eq_add_neg] -set_option backward.isDefEq.respectTransparency false in /-- The metric `ηᵃᵃ` as a morphism `𝟙_ (Rep ℝ (LorentzGroup d)) ⟶ ContrMod.rep ⊗ ContrMod.rep`, making its invariance under the action of `LorentzGroup d`. -/ def preContrMetric (d : ℕ := 3) : @@ -81,7 +80,6 @@ lemma preCoMetricVal_expand_tmul {d : ℕ} : preCoMetricVal d = simp [Fintype.sum_sum_type, minkowskiMatrix.inl_0_inl_0, minkowskiMatrix.inr_i_inr_i, sub_eq_add_neg] -set_option backward.isDefEq.respectTransparency false in /-- The metric `ηᵢᵢ` as a morphism `𝟙_ (Rep ℂ (LorentzGroup d))) ⟶ CoMod.rep ⊗ CoMod.rep`, making its invariance under the action of `LorentzGroup d`. -/ def preCoMetric (d : ℕ := 3) : (Representation.trivial ℝ (LorentzGroup d) ℝ).IntertwiningMap diff --git a/Physlib/Relativity/Tensors/RealTensor/ToComplex.lean b/Physlib/Relativity/Tensors/RealTensor/ToComplex.lean index df84653cdb..fe3d983ee9 100644 --- a/Physlib/Relativity/Tensors/RealTensor/ToComplex.lean +++ b/Physlib/Relativity/Tensors/RealTensor/ToComplex.lean @@ -549,7 +549,6 @@ lemma complexify_prod {n m : ℕ} erw [basisIdxCongr_eq_cast] simp -set_option backward.isDefEq.respectTransparency false in /-- The map `toComplex` commutes with prodT. -/ lemma prodT_toComplex {n m : ℕ} {c : Fin n → realLorentzTensor.Color} @@ -630,7 +629,6 @@ lemma toComplex_contrP_basisVector {n : ℕ} {c : Fin (n + 1 + 1) → realLorent rw [Pure.dropPair_basisVector, ← Tensor.basis_apply] exact congr_arg _ (funext fun m => ComponentIdx.complexify_comp_succSuccAbove b m) -set_option backward.isDefEq.respectTransparency false in /-- The map `toComplex` commutes with `contrT`. -/ lemma contrT_toComplex {n : ℕ} {c : Fin (n + 1 + 1) → realLorentzTensor.Color} {i j : Fin (n + 1 + 1)} diff --git a/Physlib/Relativity/Tensors/RealTensor/Units/Pre.lean b/Physlib/Relativity/Tensors/RealTensor/Units/Pre.lean index 605ae3f2c4..2a994ee7e7 100644 --- a/Physlib/Relativity/Tensors/RealTensor/Units/Pre.lean +++ b/Physlib/Relativity/Tensors/RealTensor/Units/Pre.lean @@ -43,7 +43,6 @@ lemma preContrCoUnitVal_expand_tmul {d : ℕ} : preContrCoUnitVal d = simp [hb] · simp -set_option backward.isDefEq.respectTransparency false in /-- The contra-co unit for complex lorentz vectors as a morphism `𝟙_ (Rep ℂ SL(2,ℂ)) ⟶ complexContr ⊗ complexCo`, manifesting the invariance under the `SL(2, ℂ)` action. -/ @@ -95,7 +94,6 @@ lemma preCoContrUnitVal_expand_tmul {d : ℕ} : preCoContrUnitVal d = simp [hb] · simp -set_option backward.isDefEq.respectTransparency false in /-- The co-contra unit for complex lorentz vectors as a morphism `𝟙_ (Rep ℝ (LorentzGroup d)) ⟶ CoMod.rep ⊗ ContrMod.rep`, manifesting the invariance under the `LorentzGroup d` action. -/ diff --git a/Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Basic.lean b/Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Basic.lean index f2f6c988c8..c07df048cb 100644 --- a/Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Basic.lean +++ b/Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Basic.lean @@ -64,7 +64,6 @@ lemma continuous_contr {T : Type} [TopologicalSpace T] (f : T → ContrMod d) (h : Continuous (fun i => (f i).toFin1dℝ)) : Continuous f := by exact continuous_induced_rng.mpr h -set_option backward.isDefEq.respectTransparency false in lemma contr_continuous {T : Type} [TopologicalSpace T] (f : ContrMod d → T) (h : Continuous (f ∘ (@ContrMod.toFin1dℝEquiv d).symm)) : Continuous f := by let x := Equiv.toHomeomorphOfIsInducing (@ContrMod.toFin1dℝEquiv d).toEquiv diff --git a/Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Contraction.lean b/Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Contraction.lean index 1d82b73a53..06fc785a6e 100644 --- a/Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Contraction.lean +++ b/Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Contraction.lean @@ -278,7 +278,6 @@ lemma nondegenerate : (∀ (x : ContrMod d), ⟪x, y⟫ₘ = 0) ↔ y = 0 := by · exact (self_parity_eq_zero_iff _).mp ((symm _ _).trans $ h _) · simp [h] -set_option backward.isDefEq.respectTransparency false in lemma matrix_apply_eq_iff_sub : ⟪x, Λ *ᵥ y⟫ₘ = ⟪x, Λ' *ᵥ y⟫ₘ ↔ ⟪x, (Λ - Λ') *ᵥ y⟫ₘ = 0 := by rw [← sub_eq_zero, ← LinearMap.map_sub, ← tmul_sub, ← ContrMod.sub_mulVec Λ Λ' y] @@ -320,7 +319,6 @@ lemma _root_.LorentzGroup.mem_iff_invariant : Λ ∈ LorentzGroup d ↔ rw [← matrix_eq_id_iff] at h exact LorentzGroup.mem_iff_dual_mul_self.mpr h -set_option backward.isDefEq.respectTransparency false in lemma _root_.LorentzGroup.mem_iff_norm : Λ ∈ LorentzGroup d ↔ ∀ (w : ContrMod d), ⟪Λ *ᵥ w, Λ *ᵥ w⟫ₘ = ⟪w, w⟫ₘ := by rw [LorentzGroup.mem_iff_invariant] diff --git a/Physlib/SpaceAndTime/Space/Derivatives/Grad.lean b/Physlib/SpaceAndTime/Space/Derivatives/Grad.lean index 47d049bc0d..0343ab3480 100644 --- a/Physlib/SpaceAndTime/Space/Derivatives/Grad.lean +++ b/Physlib/SpaceAndTime/Space/Derivatives/Grad.lean @@ -479,7 +479,6 @@ scoped[Space] notation "∇ᵈ" => distGrad -/ -set_option backward.isDefEq.respectTransparency false in lemma distGrad_inner_eq {d} (f : (Space d) →d[ℝ] ℝ) (η : 𝓢(Space d, ℝ)) (y : EuclideanSpace ℝ (Fin d)) : ⟪∇ᵈ f η, y⟫_ℝ = fderivD ℝ f η (basis.repr.symm y) := by rw [distGrad] diff --git a/Physlib/SpaceAndTime/Space/Norm/Basic.lean b/Physlib/SpaceAndTime/Space/Norm/Basic.lean index 4c495bfb07..7d6774cdbb 100644 --- a/Physlib/SpaceAndTime/Space/Norm/Basic.lean +++ b/Physlib/SpaceAndTime/Space/Norm/Basic.lean @@ -600,7 +600,6 @@ lemma gradient_dist_normPowerSeries_log_tendsTo_distGrad_norm {d : ℕ} (hd : 2 exact tendsto_const_nhds.mul ((normPowerSeries_tendsto x hx).log (norm_ne_zero_iff.mpr hx)) -set_option backward.isDefEq.respectTransparency false in lemma gradient_dist_normPowerSeries_log_tendsTo {d : ℕ} (hd : 2 ≤ d) (η : 𝓢(Space d, ℝ)) (y : EuclideanSpace ℝ (Fin d)) : Filter.Tendsto (fun n => @@ -1075,7 +1074,6 @@ lemma distDiv_inv_pow_eq_dim {d : ℕ} [NeZero d] : -/ -set_option backward.isDefEq.respectTransparency false in /-- The distributional Laplacian of `‖x‖ ^ (2 - d)` is `(2 - d) * d * volume (Metric.ball 0 1)` times the Dirac delta at the origin. For `d ≥ 3` this `‖x‖ ^ (2 - d)` is the (singular) fundamental solution of the Laplacian, and for `d = 1` it is `‖x‖`. When `d = 2` the exponent diff --git a/Physlib/Units/Examples.lean b/Physlib/Units/Examples.lean index d41f626c88..4700e685e6 100644 --- a/Physlib/Units/Examples.lean +++ b/Physlib/Units/Examples.lean @@ -111,7 +111,6 @@ def EnergyMassWithDimNot (m : WithDim M𝓭 ℝ) (E : WithDim (M𝓭 * L𝓭 * L (c : WithDim (L𝓭 * T𝓭⁻¹) ℝ) : Prop := E.1 = m.1 * c.1 -set_option backward.isDefEq.respectTransparency false in lemma energyMassWithDimNot_not_isDimensionallyCorrect : ¬ IsDimensionallyCorrect EnergyMassWithDimNot := by simp only [isDimensionallyCorrect_fun_iff, not_forall, funext_iff, scaleUnit_apply_fun] diff --git a/QuantumInfo/ForMathlib/HermitianMat/Proj.lean b/QuantumInfo/ForMathlib/HermitianMat/Proj.lean index 1370d73a09..f3969253ff 100644 --- a/QuantumInfo/ForMathlib/HermitianMat/Proj.lean +++ b/QuantumInfo/ForMathlib/HermitianMat/Proj.lean @@ -141,7 +141,6 @@ theorem projector_eq_sum_rankOne (b : OrthonormalBasis ι 𝕜 S) : convert! congr_arg ( fun x : EuclideanSpace ( _ ) n => x i ) ( h_proj j ) using 1 simp [ Matrix.sum_apply, mul_comm ] -set_option backward.isDefEq.respectTransparency false in /-- The projector onto the support of A is the sum of the projections onto the eigenvectors with non-zero eigenvalues. -/ diff --git a/QuantumInfo/ForMathlib/Majorization.lean b/QuantumInfo/ForMathlib/Majorization.lean index c6872240a9..9bb257922c 100644 --- a/QuantumInfo/ForMathlib/Majorization.lean +++ b/QuantumInfo/ForMathlib/Majorization.lean @@ -845,7 +845,6 @@ For the direct induction approach on n: Hmm, this doesn't work cleanly because log(y_i/x_i) can be negative for some i. Better approach: prove it directly using the Abel summation identity and nonnegativity of each term. -/ -set_option backward.isDefEq.respectTransparency false in lemma sum_mul_log_nonneg_of_weak_log_maj {n : ℕ} {x y : Fin n → ℝ} (hx_pos : ∀ i, 0 < x i) (hy_pos : ∀ i, 0 < y i) From 846bc6814ee141aa945d7385646b6e45385e635e Mon Sep 17 00:00:00 2001 From: aadarsh agarwal Date: Sat, 22 Aug 2026 05:12:15 -0500 Subject: [PATCH 03/20] feat(Mathematics): shared gradient lemmas, used by the oscillators (#1560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(Mathematics): gradient_add_const, gradient_const_mul, gradient_inner_self, gradient_coord Add `Physlib/Mathematics/Calculus/Gradient.lean` with the elementary gradient rules on a real Hilbert space: `gradient_add_const`, `gradient_const_mul`, `gradient_inner_self`, `gradient_const_mul_inner_self`, and `gradient_coord` for coordinate functionals on `EuclideanSpace ℝ ι`. These are the lemmas that the harmonic oscillator and damped harmonic oscillator currently prove privately and that the simple pendulum needs as well. Co-authored-by: Claude Fable 5 * refactor(ClassicalMechanics): use shared gradient lemmas in HarmonicOscillator.Basic Replace the local `gradient_inner_self`, `gradient_const_mul_inner_self` and the private `gradient_add_const'` of the harmonic oscillator by the general lemmas of `Physlib.Mathematics.Calculus.Gradient`. No statement about the harmonic oscillator changes. Also remove `toDual_symm_innerSL`: it was a helper for the deleted gradient lemmas and has no remaining use anywhere in the repository. Co-authored-by: Claude Fable 5 * refactor(ClassicalMechanics): use shared gradient_const_mul in DampedHarmonicOscillator.Basic Drop the private copy of `gradient_const_mul` in favour of the general lemma in `Physlib.Mathematics.Calculus.Gradient`. Co-authored-by: Claude Fable 5 * docs(Mathematics): docstrings and scope notes for the shared gradient lemmas Add one-line docstrings to `gradient_add_const`, `gradient_const_mul`, `gradient_inner_self`, `gradient_const_mul_inner_self` and `gradient_coord`; explain in the module overview how this file relates to `Space.Derivatives.Grad` and why it is stated over `ℝ`; drop the redundant import of `Mathlib.Analysis.InnerProductSpace.PiL2`. Co-authored-by: Claude Fable 5 * feat(Mathematics): gradient_comp_coord, the chain rule for a function of one coordinate Add `gradient_comp_coord : HasDerivAt f f' (x i) → gradient (fun y => f (y i)) x = f' • EuclideanSpace.single i 1`, the general form of the cosine-potential gradient that the simple pendulum needs. Co-authored-by: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- Physlib.lean | 1 + .../DampedHarmonicOscillator/Basic.lean | 5 - .../HarmonicOscillator/Basic.lean | 33 +---- Physlib/Mathematics/Calculus/Gradient.lean | 140 ++++++++++++++++++ 4 files changed, 146 insertions(+), 33 deletions(-) create mode 100644 Physlib/Mathematics/Calculus/Gradient.lean diff --git a/Physlib.lean b/Physlib.lean index 0f47b72002..8466cb3688 100644 --- a/Physlib.lean +++ b/Physlib.lean @@ -99,6 +99,7 @@ public import Physlib.FluidDynamics.ThermodynamicCauchyFlow.Bernoulli public import Physlib.FluidDynamics.ThermodynamicCauchyFlow.Isentropic public import Physlib.Mathematics.Calculus.AdjFDeriv public import Physlib.Mathematics.Calculus.Divergence +public import Physlib.Mathematics.Calculus.Gradient public import Physlib.Mathematics.Calculus.ParametricIntegration public import Physlib.Mathematics.Calculus.Wirtinger.Basic public import Physlib.Mathematics.Calculus.Wirtinger.Coordinate diff --git a/Physlib/ClassicalMechanics/DampedHarmonicOscillator/Basic.lean b/Physlib/ClassicalMechanics/DampedHarmonicOscillator/Basic.lean index 197c1de3d9..2f7b49fbff 100644 --- a/Physlib/ClassicalMechanics/DampedHarmonicOscillator/Basic.lean +++ b/Physlib/ClassicalMechanics/DampedHarmonicOscillator/Basic.lean @@ -504,11 +504,6 @@ lagrangian, using that the gradient scales with the constant `exp (γ/m * t)`. -/ -private lemma gradient_const_mul {f : EuclideanSpace ℝ (Fin 1) → ℝ} {x : EuclideanSpace ℝ (Fin 1)} - (c : ℝ) (hf : DifferentiableAt ℝ f x) : - gradient (fun y => c * f y) x = c • gradient f x := by - simp [gradient, fderiv_const_mul hf, map_smul] - lemma gradient_lagrangian_position_eq (t : Time) (x v : EuclideanSpace ℝ (Fin 1)) : gradient (fun x => S.lagrangian t x v) x = -(exp (S.γ / S.m * t) * S.k) • x := by have hf : DifferentiableAt ℝ (fun y => S.toHarmonicOscillator.lagrangian t y v) x := by diff --git a/Physlib/ClassicalMechanics/HarmonicOscillator/Basic.lean b/Physlib/ClassicalMechanics/HarmonicOscillator/Basic.lean index 7147a691cd..4aad8f5a13 100644 --- a/Physlib/ClassicalMechanics/HarmonicOscillator/Basic.lean +++ b/Physlib/ClassicalMechanics/HarmonicOscillator/Basic.lean @@ -7,6 +7,7 @@ module public import Physlib.ClassicalMechanics.EulerLagrange public import Physlib.ClassicalMechanics.HamiltonsEquations +public import Physlib.Mathematics.Calculus.Gradient public import Mathlib.Algebra.Order.Archimedean.Real.Hom /-! @@ -337,25 +338,6 @@ lemma contDiff_lagrangian (n : WithTop ℕ∞) : ContDiff ℝ n ↿S.lagrangian rw [lagrangian_eq] fun_prop -lemma toDual_symm_innerSL (x : EuclideanSpace ℝ (Fin 1)) : - (InnerProductSpace.toDual ℝ (EuclideanSpace ℝ (Fin 1))).symm (innerSL ℝ x) = x := - (InnerProductSpace.toDual ℝ (EuclideanSpace ℝ (Fin 1))).symm_apply_apply x - -lemma gradient_inner_self (x : EuclideanSpace ℝ (Fin 1)) : - gradient (fun y : EuclideanSpace ℝ (Fin 1) => ⟪y, y⟫_ℝ) x = (2 : ℝ) • x := by - refine ext_inner_right (𝕜 := ℝ) fun y => ?_ - unfold gradient - rw [InnerProductSpace.toDual_symm_apply, - fderiv_inner_apply (𝕜 := ℝ) differentiableAt_fun_id differentiableAt_fun_id] - simp [real_inner_comm, inner_smul_right, two_mul] - -lemma gradient_const_mul_inner_self (c : ℝ) (x : EuclideanSpace ℝ (Fin 1)) : - gradient (fun y : EuclideanSpace ℝ (Fin 1) => c * ⟪y, y⟫_ℝ) x = (2 * c) • x := by - unfold gradient - rw [fderiv_const_mul (by fun_prop) c, map_smul] - show c • gradient (fun y : EuclideanSpace ℝ (Fin 1) => ⟪y, y⟫_ℝ) x = (2 * c) • x - rw [gradient_inner_self, smul_smul, mul_comm] - /-! #### D.1.3. Gradients of the lagrangian @@ -365,18 +347,13 @@ position and velocity. -/ -private lemma gradient_add_const' {f : EuclideanSpace ℝ (Fin 1) → ℝ} {c : ℝ} - (x : EuclideanSpace ℝ (Fin 1)) : - gradient (fun y => f y + c) x = gradient f x := - congrArg (InnerProductSpace.toDual ℝ (EuclideanSpace ℝ (Fin 1))).symm (fderiv_add_const c) - lemma gradient_lagrangian_position_eq (t : Time) (x : EuclideanSpace ℝ (Fin 1)) (v : EuclideanSpace ℝ (Fin 1)) : gradient (fun x => lagrangian S t x v) x = - S.k • x := by have h_eq : (fun y : EuclideanSpace ℝ (Fin 1) => lagrangian S t y v) = fun y => (-(1 / (2 : ℝ)) * S.k) * ⟪y, y⟫_ℝ + (1 / (2 : ℝ) * S.m * ⟪v, v⟫_ℝ) := by funext y; simp only [lagrangian_eq]; ring - rw [h_eq, gradient_add_const', gradient_const_mul_inner_self] + rw [h_eq, gradient_add_const, gradient_const_mul_inner_self] module lemma gradient_lagrangian_velocity_eq (t : Time) (x : EuclideanSpace ℝ (Fin 1)) @@ -386,7 +363,7 @@ lemma gradient_lagrangian_velocity_eq (t : Time) (x : EuclideanSpace ℝ (Fin 1) fun y => ((1 / (2 : ℝ)) * S.m) * ⟪y, y⟫_ℝ + (-(1 / (2 : ℝ)) * S.k * ⟪x, x⟫_ℝ) := by funext y; simp only [lagrangian_eq]; ring change gradient (fun y : EuclideanSpace ℝ (Fin 1) => lagrangian S t x y) v = S.m • v - rw [h_eq, gradient_add_const', gradient_const_mul_inner_self] + rw [h_eq, gradient_add_const, gradient_const_mul_inner_self] module /-! @@ -679,7 +656,7 @@ lemma gradient_hamiltonian_position_eq (t : Time) (x : EuclideanSpace ℝ (Fin 1 simp only [hamiltonian_eq] ring change gradient (fun y : EuclideanSpace ℝ (Fin 1) => hamiltonian S t p y) x = S.k • x - rw [h_eq, gradient_add_const', gradient_const_mul_inner_self] + rw [h_eq, gradient_add_const, gradient_const_mul_inner_self] module lemma gradient_hamiltonian_momentum_eq (t : Time) (x : EuclideanSpace ℝ (Fin 1)) @@ -691,7 +668,7 @@ lemma gradient_hamiltonian_momentum_eq (t : Time) (x : EuclideanSpace ℝ (Fin 1 funext y simp only [hamiltonian_eq] change gradient (fun y : EuclideanSpace ℝ (Fin 1) => hamiltonian S t y x) p = (1 / S.m) • p - rw [h_eq, gradient_add_const', gradient_const_mul_inner_self] + rw [h_eq, gradient_add_const, gradient_const_mul_inner_self] module /-! diff --git a/Physlib/Mathematics/Calculus/Gradient.lean b/Physlib/Mathematics/Calculus/Gradient.lean new file mode 100644 index 0000000000..5eabec0ed2 --- /dev/null +++ b/Physlib/Mathematics/Calculus/Gradient.lean @@ -0,0 +1,140 @@ +/- +Copyright (c) 2026 Aadarsh Agarwal. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Aadarsh Agarwal +-/ +module + +public import Mathlib.Analysis.Calculus.Gradient.Basic +public import Mathlib.Analysis.InnerProductSpace.Calculus +/-! + +# Elementary rules for the gradient + +## i. Overview + +Mathlib defines the gradient `∇ f x` of a real-valued function on a real Hilbert space as the +Riesz representative of its Fréchet derivative, but records no rules for the algebraic operations +on `f` beyond constants. This file collects the elementary rules used throughout the classical +mechanics of Physlib: a gradient is unchanged by adding a constant, it commutes with +multiplication by a constant, the gradient of the quadratic form `⟪y, y⟫` is `2 • y`, and the +gradient of a coordinate functional on Euclidean space is the corresponding basis vector. + +These are the rules needed to differentiate Lagrangians and Hamiltonians of the form +`kinetic − potential` with respect to positions and velocities. + +These are rules for Mathlib's `gradient` on an abstract real Hilbert space. They are distinct from +`Physlib.SpaceAndTime.Space.Derivatives.Grad`, whose `Space.grad` is a coordinate-valued operator on +the structure `Space d`; nothing there applies to `EuclideanSpace ℝ (Fin 1)` or to a general inner +product space. The file is deliberately real: two of its rules (`gradient_const_mul` and +`gradient_inner_self`) are specific to real scalars, so the remaining ones are stated over +`ℝ` as well. + +## ii. Key results + +- `gradient_add_const` : `∇ (f + c) = ∇ f`. +- `gradient_const_mul` : `∇ (c * f) = c • ∇ f` for differentiable `f`. +- `gradient_inner_self` : `∇ (fun y => ⟪y, y⟫) x = 2 • x`. +- `gradient_const_mul_inner_self` : `∇ (fun y => c * ⟪y, y⟫) x = (2 * c) • x`. +- `gradient_coord` : `∇ (fun y => y i) x = EuclideanSpace.single i 1`. +- `gradient_comp_coord` : `∇ (fun y => f (y i)) x = f' • EuclideanSpace.single i 1` when + `HasDerivAt f f' (x i)`. + +## iii. Table of contents + +- A. Gradients and constants +- B. Gradients of quadratic forms +- C. Coordinate functionals on Euclidean space + +## iv. References + +- Mathlib, `Mathlib.Analysis.Calculus.Gradient.Basic`. + +-/ + +@[expose] public section + +noncomputable section + +open InnerProductSpace + +variable {F : Type*} [NormedAddCommGroup F] [InnerProductSpace ℝ F] [CompleteSpace F] + +/-! + +## A. Gradients and constants + +Adding a constant does not change the Fréchet derivative, hence not the gradient; multiplying by a +constant scales both. + +-/ + +/-- Adding a constant to a function does not change its gradient. -/ +lemma gradient_add_const {f : F → ℝ} (c : ℝ) (x : F) : + gradient (fun y => f y + c) x = gradient f x := by + unfold gradient + rw [fderiv_add_const] + +/-- The gradient of a constant multiple of a differentiable function is the constant multiple of +the gradient. -/ +lemma gradient_const_mul {f : F → ℝ} {x : F} (c : ℝ) (hf : DifferentiableAt ℝ f x) : + gradient (fun y => c * f y) x = c • gradient f x := by + unfold gradient + rw [fderiv_const_mul hf, map_smul] + +/-! + +## B. Gradients of quadratic forms + +The quadratic form `y ↦ ⟪y, y⟫` has derivative `v ↦ 2 ⟪x, v⟫` at `x`, whose Riesz representative +is `2 • x`. + +-/ + +/-- The gradient of `y ↦ ⟪y, y⟫` at `x` is `2 • x`. -/ +lemma gradient_inner_self (x : F) : gradient (fun y : F => ⟪y, y⟫_ℝ) x = (2 : ℝ) • x := by + refine ext_inner_right (𝕜 := ℝ) fun y => ?_ + unfold gradient + rw [toDual_symm_apply, + fderiv_inner_apply (𝕜 := ℝ) differentiableAt_fun_id differentiableAt_fun_id] + simp [real_inner_comm, inner_smul_right, two_mul] + +/-- The gradient of `y ↦ c * ⟪y, y⟫` at `x` is `(2 * c) • x`. -/ +lemma gradient_const_mul_inner_self (c : ℝ) (x : F) : + gradient (fun y : F => c * ⟪y, y⟫_ℝ) x = (2 * c) • x := by + rw [gradient_const_mul c (differentiableAt_fun_id.inner ℝ differentiableAt_fun_id), + gradient_inner_self, smul_smul, mul_comm] + +/-! + +## C. Coordinate functionals on Euclidean space + +The coordinate functional `y ↦ y i` on `EuclideanSpace ℝ ι` is the continuous linear map +`EuclideanSpace.proj i`, whose Riesz representative is the basis vector `EuclideanSpace.single i 1`. + +-/ + +/-- The gradient of the `i`-th coordinate functional on Euclidean space is the `i`-th basis +vector. -/ +lemma gradient_coord {ι : Type*} [Fintype ι] [DecidableEq ι] (i : ι) (x : EuclideanSpace ℝ ι) : + gradient (fun y : EuclideanSpace ℝ ι => y i) x = EuclideanSpace.single i 1 := by + have h : HasFDerivAt (fun y : EuclideanSpace ℝ ι => y i) + (innerSL ℝ (EuclideanSpace.single i (1 : ℝ))) x := + (EuclideanSpace.proj (𝕜 := ℝ) i).hasFDerivAt.congr_fderiv + (by ext y; simp [EuclideanSpace.inner_single_left]) + exact h.hasGradientAt.gradient.trans ((toDual ℝ _).symm_apply_apply _) + +/-- Chain rule for a function of one coordinate: the gradient of `y ↦ f (y i)` at `x` is +`f' • EuclideanSpace.single i 1`, where `f'` is the derivative of `f` at `x i`. -/ +lemma gradient_comp_coord {ι : Type*} [Fintype ι] [DecidableEq ι] {f : ℝ → ℝ} {f' : ℝ} + (i : ι) (x : EuclideanSpace ℝ ι) (hf : HasDerivAt f f' (x i)) : + gradient (fun y : EuclideanSpace ℝ ι => f (y i)) x = f' • EuclideanSpace.single i 1 := by + have h : HasFDerivAt (fun y : EuclideanSpace ℝ ι => f (y i)) + (innerSL ℝ (f' • EuclideanSpace.single i (1 : ℝ))) x := + (hf.comp_hasFDerivAt x (EuclideanSpace.proj (𝕜 := ℝ) i).hasFDerivAt).congr_fderiv + (by ext y; simp [EuclideanSpace.inner_single_left, smul_eq_mul]) + exact h.hasGradientAt.gradient.trans ((toDual ℝ _).symm_apply_apply _) + +end + +end From 7b6e0fee67033b61f9fc9061e190de5130925fcb Mon Sep 17 00:00:00 2001 From: Utkarsh Raj Date: Sat, 22 Aug 2026 21:27:22 +0530 Subject: [PATCH 04/20] Fix tensor product documentation examples (#1562) --- Physlib/Relativity/Tensors/Elab.lean | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Physlib/Relativity/Tensors/Elab.lean b/Physlib/Relativity/Tensors/Elab.lean index 7244349571..d0353081d3 100644 --- a/Physlib/Relativity/Tensors/Elab.lean +++ b/Physlib/Relativity/Tensors/Elab.lean @@ -29,11 +29,11 @@ public import Physlib.Relativity.Tensors.Tensorial - If `a ∈ k` then `{a •ₜ T | μ ν}ᵀ` is `smulNode a (tensorNode T)`. - If `g ∈ S.G` then `{g •ₐ T | μ ν}ᵀ` is `actionNode g (tensorNode T)`. - Suppose `T2` is a tensor with color `![c3]`. - Then `{T | μ ν ⊗ T2 | σ}ᵀ` is `prodNode (tensorNode T1) (tensorNode T2)`. + Then `{T | μ ν ⊗ T2 | σ}ᵀ` is `prodNode (tensorNode T) (tensorNode T2)`. - If `T3` is a tensor with color `![S.τ c1, S.τ c2]`, then - `{T | μ ν ⊗ T3 | μ σ}ᵀ` is `contr 0 1 _ (prodNode (tensorNode T1) (tensorNode T3))`. + `{T | μ ν ⊗ T3 | μ σ}ᵀ` is `contr 0 1 _ (prodNode (tensorNode T) (tensorNode T3))`. `{T | μ ν ⊗ T3 | μ ν }ᵀ` is - `contr 0 0 _ (contr 0 1 _ (prodNode (tensorNode T1) (tensorNode T3)))`. + `contr 0 0 _ (contr 0 1 _ (prodNode (tensorNode T) (tensorNode T3)))`. - If `T4` is a tensor with color `![c2, c1]` then `{T | μ ν + T4 | ν μ }ᵀ`is `addNode (tensorNode T) (perm _ (tensorNode T4))` where `_` is the permutation of the two indices of `T4`. From 8381cbfb96624ad9ca158130147ad25ab5d61aca Mon Sep 17 00:00:00 2001 From: aadarsh agarwal Date: Sun, 23 Aug 2026 08:20:48 -0500 Subject: [PATCH 05/20] feat(ClassicalMechanics): the simple pendulum's configuration space (#1561) --- Physlib.lean | 1 + .../ClassicalMechanics/Pendulum/API-map.yaml | 28 +- .../Pendulum/SimplePendulum/API-map.yaml | 49 +++ .../SimplePendulum/Geometric/Basic.lean | 406 ++++++++++++++++++ 4 files changed, 469 insertions(+), 15 deletions(-) create mode 100644 Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml create mode 100644 Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Geometric/Basic.lean diff --git a/Physlib.lean b/Physlib.lean index 8466cb3688..29b99413cf 100644 --- a/Physlib.lean +++ b/Physlib.lean @@ -17,6 +17,7 @@ public import Physlib.ClassicalMechanics.Mass.MassUnit public import Physlib.ClassicalMechanics.OrbitalMechanics.VisViva public import Physlib.ClassicalMechanics.Pendulum.CoplanarDoublePendulum public import Physlib.ClassicalMechanics.Pendulum.MiscellaneousPendulumPivotMotions +public import Physlib.ClassicalMechanics.Pendulum.SimplePendulum.Geometric.Basic public import Physlib.ClassicalMechanics.Pendulum.SlidingPendulum public import Physlib.ClassicalMechanics.RigidBody.AngularMomentum public import Physlib.ClassicalMechanics.RigidBody.AngularVelocity diff --git a/Physlib/ClassicalMechanics/Pendulum/API-map.yaml b/Physlib/ClassicalMechanics/Pendulum/API-map.yaml index cebdc018a3..f7f3c67b25 100644 --- a/Physlib/ClassicalMechanics/Pendulum/API-map.yaml +++ b/Physlib/ClassicalMechanics/Pendulum/API-map.yaml @@ -9,13 +9,13 @@ Overview: | covers the pendulum problems of Landau and Lifshitz, Mechanics, 3rd ed., Chapter 1, Section 5. - At present only the sliding pendulum has a defined configuration space, with - the horizontal support position and the string angle as its generalized - coordinates. The coplanar double pendulum's configuration space is declared but - not yet defined, and the miscellaneous pivot-motion problems have documentation - only. The remaining requirements, a manifold structure on the configuration - space, a map into real space, trajectories, and the lagrangian, are open and - recorded below with location N/A. + The sliding pendulum has a defined configuration space in the generalized coordinates of the + support position and the string angle. The simple pendulum's configuration space, an angle + modulo a full turn, carries the manifold structure and the map into `Space`; it has its own API + map in `Physlib/ClassicalMechanics/Pendulum/SimplePendulum`. The coplanar double pendulum's + configuration space is declared but not yet defined, and the miscellaneous pivot-motion problems + have documentation only. Trajectories and the lagrangian remain open and are recorded below with + location N/A. ParentAPIs: - Classical mechanics Lagrangian (Physlib/ClassicalMechanics/Lagrangian) @@ -33,15 +33,13 @@ Requirements: done: true location: Physlib/ClassicalMechanics/Pendulum/SlidingPendulum.lean (ConfigurationSpace) - - description: The API shall contain the structure of a manifold on the configuration space. - done: false - location: "N/A" + - description: The API contains the structure of a manifold on the configuration space (for the simple pendulum). + done: true + location: Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Geometric/Basic.lean (SimplePendulum.ConfigurationSpace.instIsManifold) - - description: > - The API shall contain a map from the configuration space to `Space`, giving the - position of the pendulum in real space. - done: false - location: "N/A" + - description: The API contains a map from the configuration space to `Space`, giving the position of the pendulum in real space (for the simple pendulum). + done: true + location: Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Geometric/Basic.lean (SimplePendulum.ConfigurationSpace.toSpace) - description: The API shall contain the definition of a trajectory based on the configuration space. done: false diff --git a/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml new file mode 100644 index 0000000000..63accba893 --- /dev/null +++ b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml @@ -0,0 +1,49 @@ +version: v0.1 + +Title: Simple pendulum + +Overview: | + A simple pendulum is a bob on a rigid massless rod of length ℓ, pinned at a pivot and swinging + in a vertical plane under gravity g. Its configuration is the angle of the rod from the downward + vertical, taken modulo a full turn, so the configuration space is a circle; the position of the + bob in the plane is (ℓ sin θ, −ℓ cos θ). The motion is governed by θ̈ + (g/ℓ) sin θ = 0. For + small amplitudes it is harmonic with period 2π√(ℓ/g); for librations (amplitudes below the + inverted position) the period grows with the amplitude and is given by a complete elliptic + integral. This API records the configuration space with its manifold structure and its + embedding into physical space; the dynamics, the small-angle limit and the period follow in + later modules. + +ParentAPIs: + - "Space (Physlib/SpaceAndTime/Space)" + - "Configuration space for pendulum (Physlib/ClassicalMechanics/Pendulum)" + +References: + - Landau & Lifshitz, Mechanics, 3rd Edition, Chapter 1 (The Equations of motion), Section 5 (The Lagrangian for a system of particles). + - Landau & Lifshitz, Mechanics, 3rd Edition, Chapter 3 (Integration of the equations of motion), Section 11 (Motion in one dimension). + - Landau & Lifshitz, Mechanics, 3rd Edition, Chapter 5 (Small oscillations), Section 21 (Free oscillations in one dimension). + +Requirements: + + - description: The key data structure, the configuration space of the simple pendulum, is defined as the angle of the rod from the downward vertical modulo a full turn. + done: true + location: Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Geometric/Basic.lean (SimplePendulum.ConfigurationSpace, SimplePendulum.ConfigurationSpace.circleHomeomorph) + + - description: The API contains the structure of a manifold on the configuration space. + done: true + location: Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Geometric/Basic.lean (SimplePendulum.ConfigurationSpace.instChartedSpace, SimplePendulum.ConfigurationSpace.instIsManifold) + + - description: The API contains the angular lift from the real line to the configuration space, periodic with period 2π. + done: true + location: Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Geometric/Basic.lean (SimplePendulum.ConfigurationSpace.ofAngle, SimplePendulum.ConfigurationSpace.ofAngle_periodic, SimplePendulum.ConfigurationSpace.ofAngle_eq_iff) + + - description: The API contains a map from the configuration space to `Space`, giving the position of the bob in real space, together with the rod-length constraint. + done: true + location: Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Geometric/Basic.lean (SimplePendulum.ConfigurationSpace.toSpace, SimplePendulum.ConfigurationSpace.toSpace_ofAngle, SimplePendulum.ConfigurationSpace.toSpace_norm) + + - description: The API shall contain the definition of a trajectory based on the configuration space. + done: false + location: N/A + + - description: The API shall contain the Lagrangian of the simple pendulum and its equation of motion. + done: false + location: N/A diff --git a/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Geometric/Basic.lean b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Geometric/Basic.lean new file mode 100644 index 0000000000..c82f4e8da6 --- /dev/null +++ b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Geometric/Basic.lean @@ -0,0 +1,406 @@ +/- +Copyright (c) 2026 Aadarsh Agarwal. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Aadarsh Agarwal +-/ +module + +public import Physlib.SpaceAndTime.Space.Module +public import Mathlib.Analysis.SpecialFunctions.Complex.Circle +public import Mathlib.Geometry.Manifold.Instances.Sphere +public import Mathlib.Topology.Covering.AddCircle +/-! + +# Configuration space of the simple pendulum + +## i. Overview + +A simple pendulum is a bob fixed to one end of a rigid massless rod of length `ℓ`, the other end +of which is pinned at a pivot, swinging in a vertical plane under gravity. Its position is fixed by +the angle of the rod from the downward vertical, and two angles that differ by a full turn describe +the same position. The configuration space is therefore a circle. + +We record a configuration by its angle modulo `2π`, i.e. by an element of `Real.Angle`, as the +sliding pendulum does (`Physlib.ClassicalMechanics.Pendulum.SlidingPendulum`). The circle carries +the structure of a compact analytic one-dimensional manifold; we obtain it by identifying the +configuration space with Mathlib's unit circle `Circle` and pulling back its charts. The smooth +identification of the configuration space with `Circle` (the analogue of the harmonic oscillator's +`valDiffeomorph`) is deferred to a later module. The real-valued angle that is used to +write down the dynamics is a lift of the configuration along the covering map +`ℝ → ConfigurationSpace`; it is not a chart, and two lifts differing by `2π n` describe the same +configuration. Finally the position of the bob in the plane is recorded by `toSpace ℓ`, which sends +the configuration at angle `θ` to `(ℓ sin θ, -ℓ cos θ)`: the pivot is the origin and the second axis +points upwards. + +## ii. Key results + +- `ConfigurationSpace` : the configuration space of the planar simple pendulum. +- `ConfigurationSpace.circleHomeomorph` : its identification with the unit circle, with inverse + `ConfigurationSpace.ofCircle` (`toCircle_ofCircle`, `ofCircle_toCircle`). +- `ConfigurationSpace.instChartedSpace`, `ConfigurationSpace.instIsManifold` : the analytic + manifold structure, pulled back from `Circle` (`chartAt_source`, `chartAt_target`). +- `ConfigurationSpace.ofAngle` : the angular lift `ℝ → ConfigurationSpace`, periodic with period + `2π`, continuous, surjective, analytic, and a covering map (`isCoveringMap_ofAngle`). +- `ConfigurationSpace.toSpace` : the position of the bob in `Space 2`, with + `toSpace_ofAngle` and the rod-length constraint `toSpace_norm`; for `ℓ ≠ 0` it is a closed + embedding (`toSpace_isClosedEmbedding`). + +## iii. Table of contents + +- A. The configuration space type +- B. Topology and identification with the unit circle +- C. Manifold structure +- D. The angular lift +- E. Map to physical space + +## iv. References + +- Landau & Lifshitz, Mechanics, 3rd ed., §5, Problems 1–3 (pendulum configurations). +- Mathlib, `Mathlib.Geometry.Manifold.Instances.Sphere` (the manifold structure on `Circle`). + +-/ + +@[expose] public section + +noncomputable section + +open scoped Manifold ContDiff + +namespace ClassicalMechanics +namespace SimplePendulum + +/-! + +## A. The configuration space type + +A configuration is the angle of the rod from the downward vertical, taken modulo a full turn. + +-/ + +/-- The configuration space of the planar simple pendulum: the angle of the rod from the downward + vertical, modulo `2π`. -/ +structure ConfigurationSpace where + /-- The angle of the rod from the downward vertical, modulo `2π`. -/ + angle : Real.Angle + +namespace ConfigurationSpace + +/-- Two configurations are equal precisely when their angles are equal. -/ +@[ext] +lemma ext {p q : ConfigurationSpace} (h : p.angle = q.angle) : p = q := by + cases p; cases q; cases h; rfl + +/-! + +## B. Topology and identification with the unit circle + +The topology is that of `Real.Angle`; composing with Mathlib's identification of `Real.Angle` +(the additive circle of period `2π`) with the unit circle `Circle ⊆ ℂ` gives a homeomorphism +`ConfigurationSpace ≃ₜ Circle`, through which the circle's compactness and Hausdorff property +transfer. + +-/ + +/-- The identification of the configuration space with `Real.Angle`. -/ +def angleEquiv : ConfigurationSpace ≃ Real.Angle where + toFun := angle + invFun φ := ⟨φ⟩ + left_inv q := by cases q; rfl + right_inv φ := rfl + +/-- The topology of the configuration space, induced from `Real.Angle`. -/ +instance instTopologicalSpace : TopologicalSpace ConfigurationSpace := + TopologicalSpace.induced angle inferInstance + +/-- The identification with `Real.Angle` as a homeomorphism. -/ +def angleHomeomorph : ConfigurationSpace ≃ₜ Real.Angle where + toEquiv := angleEquiv + continuous_toFun := continuous_induced_dom + continuous_invFun := continuous_induced_rng.mpr continuous_id + +/-- The point of the unit circle `e^{iθ}` corresponding to a configuration at angle `θ`. -/ +def toCircle (q : ConfigurationSpace) : Circle := q.angle.toCircle + +/-- The identification of the configuration space with the unit circle. -/ +def circleHomeomorph : ConfigurationSpace ≃ₜ Circle := + angleHomeomorph.trans AddCircle.homeomorphCircle' + +-- `rfl` proves this because `Real.Angle.toCircle` and `AddCircle.homeomorphCircle'` are the same +-- lift of `Circle.exp`; should that stop holding definitionally, the fallback proof is +-- `Real.Angle.induction_on` with `Real.Angle.toCircle_coe` and +-- `AddCircle.homeomorphCircle'_apply_mk`. +/-- The identification with the unit circle is given by `ConfigurationSpace.toCircle`. -/ +lemma circleHomeomorph_apply (q : ConfigurationSpace) : circleHomeomorph q = q.toCircle := rfl + +/-- The configuration corresponding to a point of the unit circle. -/ +def ofCircle : Circle → ConfigurationSpace := circleHomeomorph.symm + +/-- The point of the unit circle of the configuration attached to a point of the unit circle is + that point. -/ +@[simp] +lemma toCircle_ofCircle (z : Circle) : (ofCircle z).toCircle = z := + circleHomeomorph.apply_symm_apply z + +/-- The configuration attached to the point of the unit circle of a configuration is that + configuration. -/ +@[simp] +lemma ofCircle_toCircle (q : ConfigurationSpace) : ofCircle q.toCircle = q := + circleHomeomorph.symm_apply_apply q + +/-- The configuration space is Hausdorff, being homeomorphic to the unit circle. -/ +instance instT2Space : T2Space ConfigurationSpace := circleHomeomorph.symm.t2Space + +/-- The configuration space is compact, being homeomorphic to the unit circle. -/ +instance instCompactSpace : CompactSpace ConfigurationSpace := circleHomeomorph.symm.compactSpace + +/-- The configuration space is second countable, being homeomorphic to the unit circle. -/ +instance instSecondCountableTopology : SecondCountableTopology ConfigurationSpace := + circleHomeomorph.secondCountableTopology + +/-! + +## C. Manifold structure + +The unit circle is an analytic one-dimensional manifold modelled on `EuclideanSpace ℝ (Fin 1)` +(Mathlib, via stereographic projection). We pull its atlas back along `circleHomeomorph`: a chart +of the configuration space is the identification with the circle followed by a chart of the circle. +Since the identification cancels in every change of charts, the changes of charts are exactly those +of the circle, hence analytic. + +-/ + +/-- The charts of the configuration space: the identification with the unit circle followed by a + chart of the circle. -/ +instance instChartedSpace : ChartedSpace (EuclideanSpace ℝ (Fin 1)) ConfigurationSpace where + atlas := {circleHomeomorph.toOpenPartialHomeomorph.trans e | + e ∈ atlas (EuclideanSpace ℝ (Fin 1)) Circle} + chartAt q := circleHomeomorph.toOpenPartialHomeomorph.trans + (chartAt (EuclideanSpace ℝ (Fin 1)) (circleHomeomorph q)) + mem_chart_source q := by simp + chart_mem_atlas q := ⟨_, chart_mem_atlas _ _, rfl⟩ + +/-- The chart at a configuration is the identification with the unit circle followed by the chart + of the circle at the corresponding point. -/ +lemma chartAt_eq (q : ConfigurationSpace) : + chartAt (EuclideanSpace ℝ (Fin 1)) q = + circleHomeomorph.toOpenPartialHomeomorph.trans + (chartAt (EuclideanSpace ℝ (Fin 1)) q.toCircle) := rfl + +/-- The domain of the chart at a configuration is the preimage under the identification with the + unit circle of the domain of the chart of the circle at the corresponding point. -/ +lemma chartAt_source (q : ConfigurationSpace) : + (chartAt (EuclideanSpace ℝ (Fin 1)) q).source = + circleHomeomorph ⁻¹' (chartAt (EuclideanSpace ℝ (Fin 1)) q.toCircle).source := by + rw [chartAt_eq, OpenPartialHomeomorph.trans_source] + simp + +/-- The codomain of the chart at a configuration is the codomain of the chart of the circle at the + corresponding point. -/ +lemma chartAt_target (q : ConfigurationSpace) : + (chartAt (EuclideanSpace ℝ (Fin 1)) q).target = + (chartAt (EuclideanSpace ℝ (Fin 1)) q.toCircle).target := by + rw [chartAt_eq, OpenPartialHomeomorph.trans_target] + simp + +/-- The configuration space is an analytic manifold: every change of charts is a change of charts + of the unit circle. -/ +instance instIsManifold : IsManifold (𝓡 1) ω ConfigurationSpace where + compatible := by + rintro _ _ ⟨e₁, he₁, rfl⟩ ⟨e₂, he₂, rfl⟩ + -- The identification `h` with the circle is global, so `h.symm ≫ₕ h` is the identity. + have hself : circleHomeomorph.toOpenPartialHomeomorph.symm.trans + circleHomeomorph.toOpenPartialHomeomorph = OpenPartialHomeomorph.refl Circle := by + rw [← Homeomorph.symm_toOpenPartialHomeomorph, ← Homeomorph.trans_toOpenPartialHomeomorph, + Homeomorph.symm_trans_self, Homeomorph.refl_toOpenPartialHomeomorph] + -- Hence it cancels in the change of charts, which is therefore that of the circle. + have hcancel : (circleHomeomorph.toOpenPartialHomeomorph.trans e₁).symm.trans + (circleHomeomorph.toOpenPartialHomeomorph.trans e₂) = e₁.symm.trans e₂ := by + rw [OpenPartialHomeomorph.trans_symm_eq_symm_trans_symm, OpenPartialHomeomorph.trans_assoc, + ← OpenPartialHomeomorph.trans_assoc circleHomeomorph.toOpenPartialHomeomorph.symm, + hself, OpenPartialHomeomorph.refl_trans] + rw [hcancel] + exact HasGroupoid.compatible he₁ he₂ + +/-! + +## D. The angular lift + +`ofAngle θ` is the configuration at angle `θ` from the downward vertical. It is the quotient map +`ℝ → ℝ / 2πℤ`, a covering map of the circle: it is continuous, surjective and `2π`-periodic, and +two angles give the same configuration exactly when they differ by a whole number of turns. The +dynamics of the pendulum are written for a real-valued lift of the angle; this section is what +makes different lifts describe the same configuration. In the charts pulled back from the circle +is `Circle.exp`, so it is analytic. + +-/ + +/-- The configuration at angle `θ` (measured from the downward vertical). -/ +def ofAngle (θ : ℝ) : ConfigurationSpace := ⟨θ⟩ + +/-- The angle of the configuration at angle `θ` is `θ` modulo `2π`. -/ +@[simp] +lemma ofAngle_angle (θ : ℝ) : (ofAngle θ).angle = θ := rfl + +/-- Adding a full turn to the angle leaves the configuration unchanged. -/ +lemma ofAngle_add_two_pi (θ : ℝ) : ofAngle (θ + 2 * Real.pi) = ofAngle θ := by + ext + simp [Real.Angle.coe_add, Real.Angle.coe_two_pi] + +/-- The angular lift is periodic with period `2π`. -/ +lemma ofAngle_periodic : Function.Periodic ofAngle (2 * Real.pi) := ofAngle_add_two_pi + +/-- Two angles describe the same configuration exactly when they differ by a whole number of + turns. -/ +lemma ofAngle_eq_iff (θ₁ θ₂ : ℝ) : + ofAngle θ₁ = ofAngle θ₂ ↔ ∃ n : ℤ, θ₂ = θ₁ + n * (2 * Real.pi) := by + constructor + · intro h + obtain ⟨k, hk⟩ := + Real.Angle.angle_eq_iff_two_pi_dvd_sub.mp (congrArg ConfigurationSpace.angle h) + exact ⟨-k, by push_cast; linarith⟩ + · rintro ⟨n, rfl⟩ + exact ConfigurationSpace.ext + (Real.Angle.angle_eq_iff_two_pi_dvd_sub.mpr ⟨-n, by push_cast; ring⟩) + +/-- Every configuration is the configuration at some real angle: the lift is surjective. -/ +lemma ofAngle_surjective : Function.Surjective ofAngle := by + rintro ⟨φ⟩ + induction φ using Real.Angle.induction_on + next θ => exact ⟨θ, rfl⟩ + +/-- The angular lift is continuous. -/ +@[fun_prop] +lemma continuous_ofAngle : Continuous ofAngle := + continuous_induced_rng.mpr Real.Angle.continuous_coe + +/-- The angular lift is a covering map. -/ +lemma isCoveringMap_ofAngle : IsCoveringMap ofAngle := by + have h : IsCoveringMap ((↑) : ℝ → Real.Angle) := AddCircle.isCoveringMap_coe (2 * Real.pi) + have he : ofAngle = ⇑angleHomeomorph.symm ∘ ((↑) : ℝ → Real.Angle) := rfl + rw [he] + exact h.homeomorph_comp angleHomeomorph.symm + +/-- The configuration at angle `θ` corresponds to the point `e^{iθ}` of the unit circle. -/ +@[simp] +lemma toCircle_ofAngle (θ : ℝ) : (ofAngle θ).toCircle = Circle.exp θ := Real.Angle.toCircle_coe θ + +/-- The configuration of a point `e^{iθ}` of the unit circle is `ofAngle θ`. -/ +@[simp] +lemma ofCircle_circleExp (θ : ℝ) : ofCircle (Circle.exp θ) = ofAngle θ := by + rw [← toCircle_ofAngle, ofCircle_toCircle] + +/-- The angular lift is analytic: read in the charts pulled back from the circle it is + `Circle.exp`. -/ +lemma contMDiff_ofAngle : ContMDiff 𝓘(ℝ, ℝ) (𝓡 1) ω ofAngle := by + rw [contMDiff_iff] + refine ⟨continuous_ofAngle, fun x y => ?_⟩ + have h := (contMDiff_iff.mp (contMDiff_circleExp (m := ω))).2 x y.toCircle + -- Two goals remain: the map read in the charts, and the domain on which it is read. + convert h using 2 + · rfl + · ext θ + simp [chartAt_eq, circleHomeomorph_apply, toCircle_ofAngle] + +/-- The cosine of the angle of a configuration. -/ +def cos (q : ConfigurationSpace) : ℝ := Real.Angle.cos q.angle + +/-- The sine of the angle of a configuration. -/ +def sin (q : ConfigurationSpace) : ℝ := Real.Angle.sin q.angle + +/-- The cosine of a configuration is the cosine of its angle. -/ +lemma cos_angle (q : ConfigurationSpace) : q.cos = Real.Angle.cos q.angle := rfl + +/-- The sine of a configuration is the sine of its angle. -/ +lemma sin_angle (q : ConfigurationSpace) : q.sin = Real.Angle.sin q.angle := rfl + +/-- The cosine of the configuration at angle `θ` is `cos θ`. -/ +@[simp] +lemma cos_ofAngle (θ : ℝ) : (ofAngle θ).cos = Real.cos θ := Real.Angle.cos_coe θ + +/-- The sine of the configuration at angle `θ` is `sin θ`. -/ +@[simp] +lemma sin_ofAngle (θ : ℝ) : (ofAngle θ).sin = Real.sin θ := Real.Angle.sin_coe θ + +/-- The Pythagorean identity for the angle of a configuration. -/ +lemma cos_sq_add_sin_sq (q : ConfigurationSpace) : q.cos ^ 2 + q.sin ^ 2 = 1 := + Real.Angle.cos_sq_add_sin_sq q.angle + +/-- The cosine of the angle depends continuously on the configuration. -/ +@[fun_prop] +lemma continuous_cos : Continuous (cos : ConfigurationSpace → ℝ) := + Real.Angle.continuous_cos.comp continuous_induced_dom + +/-- The sine of the angle depends continuously on the configuration. -/ +@[fun_prop] +lemma continuous_sin : Continuous (sin : ConfigurationSpace → ℝ) := + Real.Angle.continuous_sin.comp continuous_induced_dom + +/-! + +## E. Map to physical space + +The pivot is the origin of the plane `Space 2`, the first coordinate is horizontal and the second +points upwards. A rod of length `ℓ` at angle `θ` from the downward vertical places the bob at +`(ℓ sin θ, -ℓ cos θ)`; at `θ = 0` the bob hangs straight down at `(0, -ℓ)`. The bob lies on the +circle of radius `|ℓ|` about the pivot — the rod-length constraint — and for `ℓ ≠ 0` the map is +injective, so the configuration is determined by the position. + +-/ + +/-- The position of the bob in the plane, for a rod of length `ℓ`. `ℓ` is not assumed positive; the + bob is at distance `|ℓ|` from the pivot. -/ +def toSpace (ℓ : ℝ) (q : ConfigurationSpace) : Space 2 := ⟨![ℓ * q.sin, -ℓ * q.cos]⟩ + +/-- The horizontal coordinate of the bob, `ℓ * q.sin`. -/ +@[simp] +lemma toSpace_apply_zero (ℓ : ℝ) (q : ConfigurationSpace) : + toSpace ℓ q 0 = ℓ * q.sin := rfl + +/-- The vertical coordinate of the bob, `-(ℓ * q.cos)`. -/ +@[simp] +lemma toSpace_apply_one (ℓ : ℝ) (q : ConfigurationSpace) : + toSpace ℓ q 1 = -(ℓ * q.cos) := neg_mul ℓ q.cos + +/-- The position of the bob for the configuration at angle `θ`. -/ +@[simp] +lemma toSpace_ofAngle (ℓ θ : ℝ) : + toSpace ℓ (ofAngle θ) = ⟨![ℓ * Real.sin θ, -ℓ * Real.cos θ]⟩ := by + simp [toSpace] + +/-- The rod-length constraint: the bob is at distance `|ℓ|` from the pivot. -/ +@[simp] +lemma toSpace_norm (ℓ : ℝ) (q : ConfigurationSpace) : ‖toSpace ℓ q‖ = |ℓ| := by + have hq : (ℓ * q.sin) ^ 2 + (-(ℓ * q.cos)) ^ 2 = ℓ ^ 2 := by + linear_combination ℓ ^ 2 * cos_sq_add_sin_sq q + rw [Space.norm_eq, Fin.sum_univ_two, toSpace_apply_zero, toSpace_apply_one, hq, + Real.sqrt_sq_eq_abs] + +/-- The position of the bob depends continuously on the configuration. -/ +@[fun_prop] +lemma continuous_toSpace (ℓ : ℝ) : Continuous (toSpace ℓ) := by + refine Space.mk_continuous.comp (continuous_pi fun i => ?_) + fin_cases i <;> simp <;> fun_prop + +/-- For a rod of nonzero length the configuration is determined by the position of the bob. -/ +lemma toSpace_injective {ℓ : ℝ} (hℓ : ℓ ≠ 0) : Function.Injective (toSpace ℓ) := by + -- Equality of angles is detected by their cosine and sine. + have key : ∀ θ ψ : Real.Angle, θ.cos = ψ.cos → θ.sin = ψ.sin → θ = ψ := by + intro θ ψ + induction θ using Real.Angle.induction_on + induction ψ using Real.Angle.induction_on + simpa using Real.Angle.cos_sin_inj + intro q₁ q₂ h + have h0 : ℓ * q₁.sin = ℓ * q₂.sin := congrArg (fun p : Space 2 => p 0) h + have h1 : -ℓ * q₁.cos = -ℓ * q₂.cos := congrArg (fun p : Space 2 => p 1) h + exact ConfigurationSpace.ext + (key _ _ (mul_left_cancel₀ (neg_ne_zero.mpr hℓ) h1) (mul_left_cancel₀ hℓ h0)) + +/-- For `ℓ ≠ 0` the position map is a closed embedding of the configuration circle. -/ +lemma toSpace_isClosedEmbedding {ℓ : ℝ} (hℓ : ℓ ≠ 0) : Topology.IsClosedEmbedding (toSpace ℓ) := + (continuous_toSpace ℓ).isClosedEmbedding (toSpace_injective hℓ) + +end ConfigurationSpace +end SimplePendulum +end ClassicalMechanics + +end From 77b3e6bb51fc01d3134f564ff455555d64bd2fc0 Mon Sep 17 00:00:00 2001 From: Tom Diem Date: Sun, 23 Aug 2026 18:33:32 +0200 Subject: [PATCH 06/20] The Lie product on observables (#1558) --- Physlib.lean | 1 + .../OperatorAlgebra/Observables/Lie.lean | 158 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 Physlib/QuantumMechanics/OperatorAlgebra/Observables/Lie.lean diff --git a/Physlib.lean b/Physlib.lean index 29b99413cf..a9c535cbb7 100644 --- a/Physlib.lean +++ b/Physlib.lean @@ -338,6 +338,7 @@ public import Physlib.QuantumMechanics.Hydrogen.Basic public import Physlib.QuantumMechanics.Hydrogen.LaplaceRungeLenzVector public import Physlib.QuantumMechanics.InfiniteSquareWell.Basic public import Physlib.QuantumMechanics.OperatorAlgebra.Basic +public import Physlib.QuantumMechanics.OperatorAlgebra.Observables.Lie public import Physlib.QuantumMechanics.Operators.AngularMomentum public import Physlib.QuantumMechanics.Operators.Commutation public import Physlib.QuantumMechanics.Operators.Covariance diff --git a/Physlib/QuantumMechanics/OperatorAlgebra/Observables/Lie.lean b/Physlib/QuantumMechanics/OperatorAlgebra/Observables/Lie.lean new file mode 100644 index 0000000000..115dec621e --- /dev/null +++ b/Physlib/QuantumMechanics/OperatorAlgebra/Observables/Lie.lean @@ -0,0 +1,158 @@ +/- +Copyright (c) 2026 Tom Ole Diem. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Tom Ole Diem +-/ +module + +public import Physlib.QuantumMechanics.OperatorAlgebra.Basic +public import Mathlib.Algebra.Lie.OfAssociative +public import Mathlib.LinearAlgebra.Complex.Module + +/-! + +# Lie structure on observables + +The observables of a complex C⋆-algebra carry the Lie bracket + + ⁅a, b⁆ = -(i / 2) (ab - ba), + +equivalently the imaginary part of their product. + +Together with the Jordan product, this gives the antisymmetric and symmetric +parts of observable multiplication. The Lie bracket measures noncommutativity +and governs infinitesimal unitary dynamics. + +-/ + +@[expose] public section + +namespace OperatorAlgebra + +open scoped ComplexOrder + +variable {A : Type*} [OperatorAlgebra A] + +namespace Observable + +/-! ## Lie bracket -/ + +/-- The observable Lie bracket is the imaginary part of the algebra product. -/ +noncomputable instance instBracket : + Bracket (Observable A) (Observable A) := + ⟨fun a b => imaginaryPart ((a : A) * (b : A))⟩ + +@[simp] +lemma coe_bracket (a b : Observable A) : + ((⁅a, b⁆ : Observable A) : A) = + (-(Complex.I / 2)) • ((a : A) * b - (b : A) * a) := by + change + (↑(imaginaryPart ((a : A) * (b : A))) : A) = + (-(Complex.I / 2)) • ((a : A) * b - (b : A) * a) + rw [imaginaryPart_apply_coe, star_mul, a.property.star_eq, b.property.star_eq] + module + +/-! ## Lie ring -/ + +lemma add_bracket (a b c : Observable A) : + ⁅a + b, c⁆ = ⁅a, c⁆ + ⁅b, c⁆ := by + change + imaginaryPart (((a + b : Observable A) : A) * (c : A)) = + imaginaryPart ((a : A) * (c : A)) + + imaginaryPart ((b : A) * (c : A)) + rw [AddSubgroup.coe_add, add_mul, map_add] + +lemma bracket_add (a b c : Observable A) : + ⁅a, b + c⁆ = ⁅a, b⁆ + ⁅a, c⁆ := by + change + imaginaryPart ((a : A) * ((b + c : Observable A) : A)) = + imaginaryPart ((a : A) * (b : A)) + + imaginaryPart ((a : A) * (c : A)) + rw [AddSubgroup.coe_add, mul_add, map_add] + +lemma bracket_self (a : Observable A) : + ⁅a, a⁆ = 0 := by + apply Subtype.ext + simp [coe_bracket] + +private lemma lie_smul (r : ℂ) (x y : A) : + ⁅x, r • y⁆ = r • ⁅x, y⁆ := by + simp only [Ring.lie_def, mul_smul_comm, smul_mul_assoc, smul_sub] + +private lemma smul_lie (r : ℂ) (x y : A) : + ⁅r • x, y⁆ = r • ⁅x, y⁆ := by + simp only [Ring.lie_def, mul_smul_comm, smul_mul_assoc, smul_sub] + +lemma leibniz_bracket (a b c : Observable A) : + ⁅a, ⁅b, c⁆⁆ = ⁅⁅a, b⁆, c⁆ + ⁅b, ⁅a, c⁆⁆ := by + apply Subtype.ext + rw [AddSubgroup.coe_add] + let s : ℂ := -(Complex.I / 2) + have hL : + ((⁅a, ⁅b, c⁆⁆ : Observable A) : A) = + (s * s) • ⁅(a : A), ⁅(b : A), (c : A)⁆⁆ := by + rw [coe_bracket, coe_bracket] + change + s • ⁅(a : A), s • ⁅(b : A), (c : A)⁆⁆ = + (s * s) • ⁅(a : A), ⁅(b : A), (c : A)⁆⁆ + rw [lie_smul, smul_smul] + have hR : + ((⁅⁅a, b⁆, c⁆ : Observable A) : A) + + ((⁅b, ⁅a, c⁆⁆ : Observable A) : A) = + (s * s) • + (⁅⁅(a : A), (b : A)⁆, (c : A)⁆ + + ⁅(b : A), ⁅(a : A), (c : A)⁆⁆) := by + rw [coe_bracket, coe_bracket, coe_bracket, coe_bracket] + change + s • ⁅s • ⁅(a : A), (b : A)⁆, (c : A)⁆ + + s • ⁅(b : A), s • ⁅(a : A), (c : A)⁆⁆ = + (s * s) • + (⁅⁅(a : A), (b : A)⁆, (c : A)⁆ + + ⁅(b : A), ⁅(a : A), (c : A)⁆⁆) + rw [smul_lie, lie_smul, smul_smul, smul_smul, ← smul_add] + rw [hL, hR] + congr 1 + simp only [Ring.lie_def] + noncomm_ring + +noncomputable instance instLieRing : + LieRing (Observable A) where + add_lie := add_bracket + lie_add := bracket_add + lie_self := bracket_self + leibniz_lie := leibniz_bracket + +/-! ## Real Lie algebra -/ + +lemma bracket_smul (t : ℝ) (a b : Observable A) : + ⁅a, t • b⁆ = t • ⁅a, b⁆ := by + change + imaginaryPart ((a : A) * ((t • b : Observable A) : A)) = + t • imaginaryPart ((a : A) * (b : A)) + rw [selfAdjoint.val_smul, mul_smul_comm] + exact map_smul (imaginaryPart : A →ₗ[ℝ] Observable A) t ((a : A) * (b : A)) + +noncomputable instance instLieAlgebra : + LieAlgebra ℝ (Observable A) where + toModule := inferInstance + lie_smul := bracket_smul + +/-! ## Elementary identities -/ + +@[simp] +lemma bracket_one_right (a : Observable A) : + ⁅a, (1 : Observable A)⁆ = 0 := by + apply Subtype.ext + rw [coe_bracket] + simp + +@[simp] +lemma bracket_one_left (a : Observable A) : + ⁅(1 : Observable A), a⁆ = 0 := by + apply Subtype.ext + rw [coe_bracket] + simp + +end Observable + +end OperatorAlgebra From 1bf77609f43dd3b88280aa41acb6da6939fef0da Mon Sep 17 00:00:00 2001 From: Utkarsh Raj Date: Mon, 24 Aug 2026 09:46:40 +0530 Subject: [PATCH 07/20] Fix position operator documentation (#1563) --- Physlib/QuantumMechanics/Operators/OneDimension/Position.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Physlib/QuantumMechanics/Operators/OneDimension/Position.lean b/Physlib/QuantumMechanics/Operators/OneDimension/Position.lean index 00bb56b1f0..18f9b26135 100644 --- a/Physlib/QuantumMechanics/Operators/OneDimension/Position.lean +++ b/Physlib/QuantumMechanics/Operators/OneDimension/Position.lean @@ -80,7 +80,7 @@ def positionOperatorUnbounded : UnboundedOperator schwartzIncl schwartzIncl_inje /-! -## Generalized eigenvectors of the momentum operator +## Generalized eigenvectors of the position operator -/ From e3cc997a40875dc9f3c5033271e7233d6c691e2f Mon Sep 17 00:00:00 2001 From: Robby Sneiderman Date: Mon, 24 Aug 2026 01:27:11 -0500 Subject: [PATCH 08/20] fix(tensors): correct cyclic permutation elaboration (#1567) Co-authored-by: Claude Opus 4.8 --- Physlib/Relativity/Tensors/Elab.lean | 34 ++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/Physlib/Relativity/Tensors/Elab.lean b/Physlib/Relativity/Tensors/Elab.lean index d0353081d3..f64ec30e46 100644 --- a/Physlib/Relativity/Tensors/Elab.lean +++ b/Physlib/Relativity/Tensors/Elab.lean @@ -267,19 +267,18 @@ def contrListAdjust (l : List (ℕ × ℕ)) : List (ℕ × ℕ) := -/ -/-- Given two lists of indices, all of which are indent, - returns the `List (ℕ)` representing the how one list - permutes into the other. -/ +/-- Given two lists of indices, all of which are identifiers, returns the `List (ℕ)` whose + `i`th entry is the position in `l2` of the `i`th index in `l1`. -/ def getPermutation (l1 l2 : List (TSyntax `indexExpr)) : TermElabM (List ℕ) := do /- Turn every index into an indent. -/ let l1' ← l1.mapM (fun x => indexToIdent x) let l2' ← l2.mapM (fun x => indexToIdent x) - /- For `l1 = [α, β, γ, δ]`, `l1enum` is `[(α, 0), (β, 1), (γ, 2), (δ, 3)]` -/ - let l1enum := l1'.zipIdx - /- For `l2 = [γ, α, δ, β]`, `l2''` is `[(γ,2), (α, 0), (δ, 3), (β, 1)]` -/ - let l2'' := l2'.filterMap - (fun x => l1enum.find? (fun y => Lean.TSyntax.getId y.1 = Lean.TSyntax.getId x)) - return l2''.map fun x => x.2 + /- For `l2 = [γ, α, δ, β]`, `l2enum` is `[(γ, 0), (α, 1), (δ, 2), (β, 3)]`. -/ + let l2enum := l2'.zipIdx + /- For `l1 = [α, β, γ, δ]`, `l1''` is `[(α, 1), (β, 3), (γ, 0), (δ, 2)]`. -/ + let l1'' := l1'.filterMap + (fun x => l2enum.find? (fun y => Lean.TSyntax.getId y.1 = Lean.TSyntax.getId x)) + return l1''.map fun x => x.2 /-- The construction of an expression corresponding to the type of a given string once parsed. -/ def stringToTerm (str : String) : TermElabM Term := do @@ -655,6 +654,23 @@ info: (contrT 0 0 1 ⋯) ((contrT 2 1 3 ⋯) ((prodT u) td)) : #guard_msgs in #check ({u | α β = u' | β α}ᵀ : Prop) +variable {V3 : Fin 3 → Type} [∀ c, AddCommGroup (V3 c)] [∀ c, Module k (V3 c)] + {basisIdx3 : Fin 3 → Type} [∀ c, Fintype (basisIdx3 c)] + [∀ c, DecidableEq (basisIdx3 c)] + {rep3 : (c : Fin 3) → Representation k G (V3 c)} + {b3 : (c : Fin 3) → Module.Basis (basisIdx3 c) k (V3 c)} + {S3 : TensorSpecies k (Fin 3) G V3 basisIdx3 rep3 b3} + {v3 : S3.Tensor ![0, 1, 2]} {v3' : S3.Tensor ![1, 2, 0]} + +-- A non-involutive reordering uses the map from target slots to source slots. +/-- info: v3 = (permT ![2, 0, 1] ⋯) v3' : Prop -/ +#guard_msgs in +#check ({v3 | α β γ = v3' | β γ α}ᵀ : Prop) + +/-- info: v3 + (permT ![2, 0, 1] ⋯) v3' : S3.Tensor ![0, 1, 2] -/ +#guard_msgs in +#check ({v3 | α β γ + v3' | β γ α}ᵀ) + variable {k : Type} [RCLike k] {C : Type} [DecidableEq C] {G : Type} [Group G] {V : C → Type} [∀ c, AddCommGroup (V c)] [∀ c, Module k (V c)] {basisIdx : C → Type} [∀ c, Fintype (basisIdx c)] [∀ c, DecidableEq (basisIdx c)] From 485cb132b971416984375699ef8380fa0d111c2b Mon Sep 17 00:00:00 2001 From: aadarsh agarwal Date: Mon, 24 Aug 2026 01:52:38 -0500 Subject: [PATCH 09/20] feat(ClassicalMechanics): the simple pendulum's dynamics, Lagrangian and equation of motion (#1564) --- Physlib.lean | 1 + .../ClassicalMechanics/Pendulum/API-map.yaml | 6 +- .../Pendulum/SimplePendulum/API-map.yaml | 10 +- .../Pendulum/SimplePendulum/Basic.lean | 754 ++++++++++++++++++ 4 files changed, 766 insertions(+), 5 deletions(-) create mode 100644 Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Basic.lean diff --git a/Physlib.lean b/Physlib.lean index a9c535cbb7..26328bc2fc 100644 --- a/Physlib.lean +++ b/Physlib.lean @@ -17,6 +17,7 @@ public import Physlib.ClassicalMechanics.Mass.MassUnit public import Physlib.ClassicalMechanics.OrbitalMechanics.VisViva public import Physlib.ClassicalMechanics.Pendulum.CoplanarDoublePendulum public import Physlib.ClassicalMechanics.Pendulum.MiscellaneousPendulumPivotMotions +public import Physlib.ClassicalMechanics.Pendulum.SimplePendulum.Basic public import Physlib.ClassicalMechanics.Pendulum.SimplePendulum.Geometric.Basic public import Physlib.ClassicalMechanics.Pendulum.SlidingPendulum public import Physlib.ClassicalMechanics.RigidBody.AngularMomentum diff --git a/Physlib/ClassicalMechanics/Pendulum/API-map.yaml b/Physlib/ClassicalMechanics/Pendulum/API-map.yaml index f7f3c67b25..64136393df 100644 --- a/Physlib/ClassicalMechanics/Pendulum/API-map.yaml +++ b/Physlib/ClassicalMechanics/Pendulum/API-map.yaml @@ -14,8 +14,10 @@ Overview: | modulo a full turn, carries the manifold structure and the map into `Space`; it has its own API map in `Physlib/ClassicalMechanics/Pendulum/SimplePendulum`. The coplanar double pendulum's configuration space is declared but not yet defined, and the miscellaneous pivot-motion problems - have documentation only. Trajectories and the lagrangian remain open and are recorded below with - location N/A. + have documentation only. The simple pendulum's Lagrangian and equation of motion on the + Euclidean lift are recorded in its own API map; the trajectory based on the configuration + space, and the Lagrangian derived from it, remain open and are recorded below with location + N/A. ParentAPIs: - Classical mechanics Lagrangian (Physlib/ClassicalMechanics/Lagrangian) diff --git a/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml index 63accba893..c6e03bb1dd 100644 --- a/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml +++ b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml @@ -10,8 +10,8 @@ Overview: | small amplitudes it is harmonic with period 2π√(ℓ/g); for librations (amplitudes below the inverted position) the period grows with the amplitude and is given by a complete elliptic integral. This API records the configuration space with its manifold structure and its - embedding into physical space; the dynamics, the small-angle limit and the period follow in - later modules. + embedding into physical space, together with the lifted Lagrangian and equation of motion; + the small-angle limit and the period follow in later modules. ParentAPIs: - "Space (Physlib/SpaceAndTime/Space)" @@ -44,6 +44,10 @@ Requirements: done: false location: N/A - - description: The API shall contain the Lagrangian of the simple pendulum and its equation of motion. + - description: The API contains the Lagrangian of the simple pendulum and its equation of motion. + done: true + location: Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Basic.lean (SimplePendulum.lagrangian, SimplePendulum.torque, SimplePendulum.EquationOfMotion, SimplePendulum.equationOfMotion_iff_scalar) + + - description: The API shall contain the equivalence of the equation of motion with the vanishing of the variational gradient of the action, and energy conservation. done: false location: N/A diff --git a/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Basic.lean b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Basic.lean new file mode 100644 index 0000000000..d6915a22e9 --- /dev/null +++ b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Basic.lean @@ -0,0 +1,754 @@ +/- +Copyright (c) 2026 Aadarsh Agarwal. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Aadarsh Agarwal +-/ +module + +public import Physlib.ClassicalMechanics.EulerLagrange +public import Physlib.Mathematics.Calculus.Gradient +/-! + +# The simple gravity pendulum + +## i. Overview + +A simple gravity pendulum is a bob of mass `m` fixed to one end of a rigid massless rod of length +`ℓ`, the other end of which is pinned at a pivot, swinging in a vertical plane under a uniform +gravitational acceleration `g`. Its configuration is the angle `θ` of the rod from the downward +vertical. The bob moves on the circle of radius `ℓ` about the pivot, so its moment of inertia +about the pivot is `I = m ℓ²` and its kinetic energy is `T = ½ I θ̇²`. Swinging out to the angle +`θ` raises the bob by `ℓ (1 - cos θ)` against gravity, so the potential energy is +`V = m g ℓ (1 - cos θ)`, normalized to vanish at the bottom of the swing. Balancing the rate of +change of the angular momentum about the pivot against the torque `-m g ℓ sin θ` of gravity gives +the equation of motion `I θ̈ = -m g ℓ sin θ`, equivalently `θ̈ + (g/ℓ) sin θ = 0`. The mass drops +out of the motion, which is governed by the single quantity `ω = √(g/ℓ)`, the angular frequency of +the small oscillations about the bottom. + +The configuration of the pendulum is genuinely an angle modulo a full turn, an element of the +circle `SimplePendulum.ConfigurationSpace`. As for the harmonic oscillator, the dynamics in this +file are written instead on the Euclidean lift `Time → EuclideanSpace ℝ (Fin 1)`: the angle is +carried by a real number, from which the configuration is recovered by +`SimplePendulum.ConfigurationSpace.ofAngle`, and the one-dimensional Euclidean space stands in for +both the configuration space and its tangent space, so that the Euler–Lagrange operator of Physlib +applies verbatim. Two lifts differing by `2π n` describe the same motion, as a subsequent +contribution proves; the connection of the model here with the geometric configuration space is +made in a later module. + +## ii. Key results + +- `SimplePendulum` contains the input data of the problem: the mass `m` of the bob, the length `ℓ` + of the rod and the gravitational acceleration `g`. +- `SimplePendulum.ω` is the angular frequency `√(g/ℓ)` of the small oscillations, and + `SimplePendulum.inertia` is the moment of inertia `m ℓ²` of the bob about the pivot. They are + tied together by `SimplePendulum.ω_sq_mul_inertia`, the identity by which the mass cancels from + the equation of motion. +- `SimplePendulum.kineticEnergy`, `SimplePendulum.potentialEnergy` and `SimplePendulum.energy` are + the energies, with the bounds `potentialEnergy_nonneg`, `potentialEnergy_le` and + `potentialEnergy_eq_zero_iff`, the gradient `gradient_potentialEnergy` of the potential and the + time derivatives `kineticEnergy_deriv`, `potentialEnergy_deriv` and `energy_deriv`. +- `SimplePendulum.lagrangian` is the Lagrangian `T - V` of the pendulum, and + `SimplePendulum.torque` is the torque about the pivot, the generalized force conjugate to + the angle. +- `SimplePendulum.EquationOfMotion` is the equation of motion `I θ̈ = τ(θ)`, with its scalar form + `equationOfMotion_iff_scalar` and its independence of the mass `equationOfMotion_iff_of_eq_ω`; + `SimplePendulum.IsSolution` is a smooth solution of it. +- `SimplePendulum.gradLagrangian` is the variational derivative of the action, computed by + `gradLagrangian_eq_eulerLagrangeOp` and `gradLagrangian_eq_torque`. + +## iii. Table of contents + +- A. The input data + - A.1. The structure of the input data + - A.2. Simple inequalities for the input data +- B. Frequency and moment of inertia + - B.1. The angular frequency + - B.2. The moment of inertia +- C. The energies + - C.1. The definitions of the energies + - C.2. Simple equalities and bounds for the energies + - C.3. Smoothness of the energies and the gradient of the potential + - C.4. Time derivatives of the energies +- D. The Lagrangian + - D.1. The definition of the Lagrangian and equalities for it + - D.2. Smoothness of the Lagrangian + - D.3. Gradients of the Lagrangian +- E. The torque and the equation of motion + - E.1. The torque + - E.2. The equation of motion + - E.3. Smooth solutions + - E.4. The scalar equation and independence of the mass +- F. The variational derivative of the action + - F.1. The definition of the variational derivative + - F.2. Equality with the Euler–Lagrange operator + - F.3. The variational derivative in terms of the torque + +## iv. References + +References for the simple gravity pendulum include: +- Landau & Lifshitz, Mechanics, 3rd ed., §5 and §21. +- Arnold, Mathematical Methods of Classical Mechanics, 2nd ed., §4. + +-/ + +@[expose] public section + +namespace ClassicalMechanics +open Real InnerProductSpace + +/-! + +## A. The input data + +We start by defining a structure containing the input data of the simple pendulum, and proving +basic properties thereof. The input data consists of the mass `m` of the bob, the length `ℓ` of +the rod, and the gravitational acceleration `g`; everything else in this file is built from these +three numbers. + +-/ + +/-! + +### A.1. The structure of the input data + +The three numbers are carried by a structure, together with the positivity assumptions: a +pendulum with a massless bob, a rod of zero length or no gravity is not a pendulum. + +-/ + +/-- The simple gravity pendulum is specified by the mass `m` of its bob, the length `ℓ` of its + rod, and the gravitational acceleration `g`. All three are assumed to be positive. The + configuration of the pendulum is the angle of the rod from the downward vertical. -/ +structure SimplePendulum where + /-- The mass of the bob. -/ + m : ℝ + /-- The length of the massless rod. -/ + ℓ : ℝ + /-- The gravitational acceleration. -/ + g : ℝ + m_pos : 0 < m + ℓ_pos : 0 < ℓ + g_pos : 0 < g + +namespace SimplePendulum + +variable (S : SimplePendulum) + +/-! + +### A.2. Simple inequalities for the input data + +The positivity of the input data is used most often through the corresponding non-vanishing +statements, which is the form in which the field-clearing tactics consume it. + +-/ + +/-- The mass of the bob is not equal to zero. -/ +@[simp] +lemma m_ne_zero : S.m ≠ 0 := S.m_pos.ne' + +/-- The length of the rod is not equal to zero. -/ +@[simp] +lemma ℓ_ne_zero : S.ℓ ≠ 0 := S.ℓ_pos.ne' + +/-- The gravitational acceleration is not equal to zero. -/ +@[simp] +lemma g_ne_zero : S.g ≠ 0 := S.g_pos.ne' + +/-! + +## B. Frequency and moment of inertia + +Two derived quantities control the dynamics of the pendulum: the angular frequency `ω = √(g/ℓ)` +of the small oscillations about the bottom of the swing, and the moment of inertia `I = m ℓ²` of +the bob about the pivot. + +The mass enters the equation of motion only through `I`, where it cancels against the mass in the +torque of gravity; what survives is `ω`. The identity performing that cancellation is +`ω_sq_mul_inertia`. + +-/ + +/-! + +### B.1. The angular frequency + +Linearizing `sin θ ≈ θ` about the bottom of the swing turns the equation of motion into that of a +harmonic oscillator of angular frequency `√(g/ℓ)`. The exact motion is not harmonic, but this +frequency is the natural time scale of the pendulum and appears throughout its analysis. + +-/ + +/-- The angular frequency of the simple pendulum, `ω`, is defined as `√(g/ℓ)`. It is the angular + frequency of the small oscillations of the pendulum about the bottom of its swing. -/ +noncomputable def ω : ℝ := √(S.g / S.ℓ) + +/-- The angular frequency of the simple pendulum is positive. -/ +@[simp] +lemma ω_pos : 0 < S.ω := sqrt_pos.mpr (div_pos S.g_pos S.ℓ_pos) + +/-- The angular frequency of the simple pendulum is not equal to zero. -/ +lemma ω_ne_zero : S.ω ≠ 0 := S.ω_pos.ne' + +/-- The square of the angular frequency of the simple pendulum is equal to `g/ℓ`. -/ +lemma ω_sq : S.ω ^ 2 = S.g / S.ℓ := sq_sqrt (div_pos S.g_pos S.ℓ_pos).le + +/-- The inverse of the square of the angular frequency of the simple pendulum is `ℓ/g`. -/ +lemma inverse_ω_sq : (S.ω ^ 2)⁻¹ = S.ℓ / S.g := by rw [ω_sq, inv_div] + +/-! + +### B.2. The moment of inertia + +The bob is a point mass at the fixed distance `ℓ` from the pivot, so the moment of inertia of the +pendulum about the pivot is `m ℓ²`. It is the coefficient relating the angular acceleration to +the torque, and so plays for the angle the role that the mass plays for a position. + +-/ + +/-- The moment of inertia of the simple pendulum about its pivot is `I = m ℓ²`, the moment of + inertia of a point mass `m` at distance `ℓ` from the axis. -/ +def inertia : ℝ := S.m * S.ℓ ^ 2 + +/-- The moment of inertia of the simple pendulum is positive. -/ +lemma inertia_pos : 0 < S.inertia := mul_pos S.m_pos (pow_pos S.ℓ_pos 2) + +/-- The moment of inertia of the simple pendulum is not equal to zero. -/ +@[simp] +lemma inertia_ne_zero : S.inertia ≠ 0 := S.inertia_pos.ne' + +/-- The square of the angular frequency times the moment of inertia is `m g ℓ`, the coefficient + appearing in the potential energy and in the torque. This is the identity by which the mass + cancels from the equation of motion. -/ +lemma ω_sq_mul_inertia : S.ω ^ 2 * S.inertia = S.m * S.g * S.ℓ := by + rw [ω_sq, inertia] + field_simp + +open Time +open scoped ContDiff + +/-! + +## C. The energies + +The simple pendulum has a kinetic energy determined by the rate of change of its angle, and a +potential energy determined by the height of the bob, hence by the angle itself. These combine to +give the total energy of the pendulum. + +Here we state and prove a number of properties of these energies, including the gradient of the +potential energy, which is the object entering the equation of motion. + +-/ + +/-! + +### C.1. The definitions of the energies + +We define the three energies; it is these energies which control the dynamics of the pendulum, +through the Lagrangian. + +-/ + +/-- The kinetic energy of the simple pendulum along a lift `θ` of the angle is + $\frac{1}{2} I ‖\dot θ‖^2$, where `I` is the moment of inertia about the pivot. -/ +noncomputable def kineticEnergy (θ : Time → EuclideanSpace ℝ (Fin 1)) : Time → ℝ := fun t => + (1 / (2 : ℝ)) * S.inertia * ⟪∂ₜ θ t, ∂ₜ θ t⟫_ℝ + +/-- The potential energy of the simple pendulum at the angle `x` is `m g ℓ (1 - cos (x 0))`, the + work done against gravity in raising the bob from the bottom of the swing. It is normalized to + vanish at the bottom. -/ +noncomputable def potentialEnergy (x : EuclideanSpace ℝ (Fin 1)) : ℝ := + S.m * S.g * S.ℓ * (1 - Real.cos (x 0)) + +/-- The energy of the simple pendulum is the kinetic energy plus the potential energy. -/ +noncomputable def energy (θ : Time → EuclideanSpace ℝ (Fin 1)) : Time → ℝ := fun t => + S.kineticEnergy θ t + S.potentialEnergy (θ t) + +/-! + +### C.2. Simple equalities and bounds for the energies + +Besides the definitional unfoldings, the potential energy of the pendulum is non-negative and +vanishes exactly at the bottom of the swing, just as the potential energy of the harmonic +oscillator is non-negative and vanishes exactly at the origin. What has no harmonic-oscillator +analogue is the upper bound: the potential energy of the pendulum is at most `2 m g ℓ`, its +value at the top of the swing. + +-/ + +/-- The kinetic energy of the simple pendulum, written out. -/ +lemma kineticEnergy_eq (θ : Time → EuclideanSpace ℝ (Fin 1)) : + S.kineticEnergy θ = fun t => (1 / (2 : ℝ)) * S.inertia * ⟪∂ₜ θ t, ∂ₜ θ t⟫_ℝ := rfl + +/-- The potential energy of the simple pendulum, written out. -/ +lemma potentialEnergy_eq (x : EuclideanSpace ℝ (Fin 1)) : + S.potentialEnergy x = S.m * S.g * S.ℓ * (1 - Real.cos (x 0)) := rfl + +/-- The energy of the simple pendulum, written out. -/ +lemma energy_eq (θ : Time → EuclideanSpace ℝ (Fin 1)) : + S.energy θ = fun t => S.kineticEnergy θ t + S.potentialEnergy (θ t) := rfl + +/-- The potential energy of the simple pendulum is non-negative, the bottom of the swing being + the lowest point of the circle on which the bob moves. -/ +lemma potentialEnergy_nonneg (x : EuclideanSpace ℝ (Fin 1)) : 0 ≤ S.potentialEnergy x := by + have hc : 0 < S.m * S.g * S.ℓ := mul_pos (mul_pos S.m_pos S.g_pos) S.ℓ_pos + have h : (0 : ℝ) ≤ 1 - Real.cos (x 0) := by + have := Real.cos_le_one (x 0) + linarith + rw [potentialEnergy_eq] + exact mul_nonneg hc.le h + +/-- The potential energy of the simple pendulum is at most `2 m g ℓ`, its value at the top of the + swing. -/ +lemma potentialEnergy_le (x : EuclideanSpace ℝ (Fin 1)) : + S.potentialEnergy x ≤ 2 * (S.m * S.g * S.ℓ) := by + have hc : 0 < S.m * S.g * S.ℓ := mul_pos (mul_pos S.m_pos S.g_pos) S.ℓ_pos + have h : 1 - Real.cos (x 0) ≤ 2 := by + have := Real.neg_one_le_cos (x 0) + linarith + calc S.potentialEnergy x = S.m * S.g * S.ℓ * (1 - Real.cos (x 0)) := S.potentialEnergy_eq x + _ ≤ S.m * S.g * S.ℓ * 2 := mul_le_mul_of_nonneg_left h hc.le + _ = 2 * (S.m * S.g * S.ℓ) := by ring + +/-- The potential energy of the simple pendulum vanishes exactly when the cosine of the angle is + equal to `1`, that is exactly at the bottom of the swing. -/ +lemma potentialEnergy_eq_zero_iff (x : EuclideanSpace ℝ (Fin 1)) : + S.potentialEnergy x = 0 ↔ Real.cos (x 0) = 1 := by + have hc : S.m * S.g * S.ℓ ≠ 0 := (mul_pos (mul_pos S.m_pos S.g_pos) S.ℓ_pos).ne' + rw [potentialEnergy_eq, mul_eq_zero, or_iff_right hc, sub_eq_zero, eq_comm] + +/-! + +### C.3. Smoothness of the energies and the gradient of the potential + +The potential energy is a smooth function of the angle, and its gradient on the one-dimensional +Euclidean lift is `m g ℓ sin θ` times the unit vector of the angular coordinate. This gradient is +what the equation of motion balances against the angular acceleration, so we record it here, once. +The subsection also records that, along a smooth lift of the angle, each of the three energies is +a differentiable function of the time — differentiability in time along the lift, as distinct +from the differentiability in the angle of the potential energy — which is the differentiability +that the time derivatives of section C.4 consume. + +-/ + +/-- The potential energy of the simple pendulum is a smooth function of the angle. -/ +@[fun_prop] +lemma potentialEnergy_contDiff (n : WithTop ℕ∞) : ContDiff ℝ n S.potentialEnergy := by + unfold potentialEnergy + fun_prop + +/-- The potential energy of the simple pendulum is a differentiable function of the angle. This + is differentiability in the angle; for differentiability in time along a smooth lift of the + angle see `potentialEnergy_differentiable`. -/ +@[fun_prop] +lemma differentiable_potentialEnergy : Differentiable ℝ S.potentialEnergy := + (S.potentialEnergy_contDiff 1).differentiable one_ne_zero + +/-- The gradient of the potential energy of the simple pendulum is `m g ℓ sin θ` times the unit + vector of the angular coordinate. -/ +lemma gradient_potentialEnergy (x : EuclideanSpace ℝ (Fin 1)) : + gradient S.potentialEnergy x = + (S.m * S.g * S.ℓ * Real.sin (x 0)) • EuclideanSpace.single 0 1 := by + have hcos : DifferentiableAt ℝ (fun y : EuclideanSpace ℝ (Fin 1) => Real.cos (y 0)) x := by + fun_prop + have h : S.potentialEnergy = fun y : EuclideanSpace ℝ (Fin 1) => + -(S.m * S.g * S.ℓ) * Real.cos (y 0) + S.m * S.g * S.ℓ := by + funext y + rw [potentialEnergy_eq] + ring + rw [h, gradient_add_const, gradient_const_mul _ hcos, + gradient_comp_coord 0 x (Real.hasDerivAt_cos (x 0))] + module + +/-- Along a smooth lift of the angle the kinetic energy is differentiable in time. -/ +@[fun_prop] +lemma kineticEnergy_differentiable (θ : Time → EuclideanSpace ℝ (Fin 1)) (hθ : ContDiff ℝ ∞ θ) : + Differentiable ℝ (S.kineticEnergy θ) := by + rw [kineticEnergy_eq] + fun_prop + +/-- Along a smooth lift of the angle the potential energy is a differentiable function of the + time. This is differentiability in time along the lift; for differentiability in the angle see + `differentiable_potentialEnergy`. -/ +@[fun_prop] +lemma potentialEnergy_differentiable (θ : Time → EuclideanSpace ℝ (Fin 1)) (hθ : ContDiff ℝ ∞ θ) : + Differentiable ℝ (fun t => S.potentialEnergy (θ t)) := by + have hd : Differentiable ℝ θ := hθ.differentiable (by simp) + fun_prop + +/-- Along a smooth lift of the angle the energy is differentiable in time. -/ +@[fun_prop] +lemma energy_differentiable (θ : Time → EuclideanSpace ℝ (Fin 1)) (hθ : ContDiff ℝ ∞ θ) : + Differentiable ℝ (S.energy θ) := by + rw [energy_eq] + fun_prop + +/-! + +### C.4. Time derivatives of the energies + +For a general smooth lift of the angle, which need not satisfy the equation of motion, we can +compute the time derivatives of the energies. Each is an inner product against the angular +velocity: the equation of motion will be exactly the statement that the two contributions cancel. + +-/ + +/-- The rate of change of the kinetic energy is the angular velocity paired with the angular + momentum's rate of change, `I θ̈`. -/ +lemma kineticEnergy_deriv (θ : Time → EuclideanSpace ℝ (Fin 1)) (hθ : ContDiff ℝ ∞ θ) : + ∂ₜ (S.kineticEnergy θ) = fun t => ⟪∂ₜ θ t, S.inertia • ∂ₜ (∂ₜ θ) t⟫_ℝ := by + funext t + unfold kineticEnergy + have hd : DifferentiableAt ℝ (∂ₜ θ) t := + (deriv_differentiable_of_contDiff θ hθ).differentiableAt + rw [Time.deriv_eq, fderiv_const_mul (by fun_prop), _root_.smul_apply, + fderiv_inner_apply (𝕜 := ℝ) hd hd, ← Time.deriv_eq] + simp [inner_smul_right, real_inner_comm] + ring + +/-- The rate of change of the potential energy is the angular velocity paired with the gradient + of the potential. -/ +lemma potentialEnergy_deriv (θ : Time → EuclideanSpace ℝ (Fin 1)) (hθ : ContDiff ℝ ∞ θ) : + ∂ₜ (fun t => S.potentialEnergy (θ t)) = + fun t => ⟪∂ₜ θ t, gradient S.potentialEnergy (θ t)⟫_ℝ := by + funext t + have hd : DifferentiableAt ℝ θ t := (hθ.differentiable (by simp)).differentiableAt + have hV : DifferentiableAt ℝ S.potentialEnergy (θ t) := + (S.potentialEnergy_contDiff 1).differentiable one_ne_zero (θ t) + have hf : HasFDerivAt (fun t => S.potentialEnergy (θ t)) _ t := + hV.hasFDerivAt.comp t hd.hasFDerivAt + rw [Time.deriv_eq, hf.fderiv] + simp [Time.deriv_eq] + +/-- The rate of change of the energy is the angular velocity paired with the sum of `I θ̈` and + the gradient of the potential; the equation of motion is exactly the vanishing of that sum. -/ +lemma energy_deriv (θ : Time → EuclideanSpace ℝ (Fin 1)) (hθ : ContDiff ℝ ∞ θ) : + ∂ₜ (S.energy θ) = + fun t => ⟪∂ₜ θ t, S.inertia • ∂ₜ (∂ₜ θ) t + gradient S.potentialEnergy (θ t)⟫_ℝ := by + unfold energy + funext t + rw [Time.deriv_eq, fderiv_fun_add (by fun_prop) (S.potentialEnergy_differentiable θ hθ t)] + simp only [_root_.add_apply, ← Time.deriv_eq, S.kineticEnergy_deriv θ hθ, + S.potentialEnergy_deriv θ hθ, ← inner_add_right] + +/-! + +## D. The Lagrangian + +The pendulum is a conservative system, so its Lagrangian is the kinetic energy minus the potential +energy, `L = ½ I θ̇² - m g ℓ (1 - cos θ)`. As for the harmonic oscillator, it is defined as a +function on phase space, of the time, the angle and the angular velocity separately; that it is +`T - V` along a lift of the angle is then a lemma rather than the definition. + +The Lagrangian carries no explicit time dependence, the pendulum being autonomous; the time +argument is kept because it is the type the Euler–Lagrange operator of Physlib expects. + +-/ + +/-! + +### D.1. The definition of the Lagrangian and equalities for it + +The Lagrangian is written directly in terms of the moment of inertia and the potential energy, +so that the equalities below are the two ways of reading it: expanded in the input data, and as +the kinetic energy minus the potential energy along a lift of the angle. + +-/ + +set_option linter.unusedVariables false in +/-- The Lagrangian of the simple pendulum, `L(t, θ, θ̇) = ½ I ‖θ̇‖² - V(θ)`, the kinetic energy + minus the potential energy as a function on phase space. It does not depend on the time. -/ +@[nolint unusedArguments] +noncomputable def lagrangian (t : Time) (x v : EuclideanSpace ℝ (Fin 1)) : ℝ := + (1 / (2 : ℝ)) * S.inertia * ⟪v, v⟫_ℝ - S.potentialEnergy x + +/-- The Lagrangian of the simple pendulum, written out in the input data. -/ +lemma lagrangian_eq : + S.lagrangian = fun _ x v => + (1 / (2 : ℝ)) * S.inertia * ⟪v, v⟫_ℝ - S.m * S.g * S.ℓ * (1 - Real.cos (x 0)) := by + funext t x v + rw [lagrangian, potentialEnergy_eq] + +/-- Along a lift of the angle the Lagrangian of the simple pendulum is the kinetic energy minus + the potential energy. -/ +lemma lagrangian_eq_kineticEnergy_sub_potentialEnergy (t : Time) + (θ : Time → EuclideanSpace ℝ (Fin 1)) : + S.lagrangian t (θ t) (∂ₜ θ t) = S.kineticEnergy θ t - S.potentialEnergy (θ t) := rfl + +/-! + +### D.2. Smoothness of the Lagrangian + +The Lagrangian is a smooth function of all of its arguments jointly. This is the hypothesis that +the Euler–Lagrange theorem of Physlib places on a Lagrangian, so it is recorded on the uncurried +form `↿S.lagrangian`. + +-/ + +/-- The Lagrangian of the simple pendulum is a smooth function of the time, the angle and the + angular velocity jointly. -/ +@[fun_prop] +lemma contDiff_lagrangian (n : WithTop ℕ∞) : ContDiff ℝ n ↿S.lagrangian := by + rw [lagrangian_eq] + fun_prop + +/-! + +### D.3. Gradients of the Lagrangian + +The Euler–Lagrange operator is built from the two partial gradients of the Lagrangian. The +gradient in the angle is minus the gradient of the potential energy, that is the torque of +section E; the gradient in the angular velocity is the angular momentum `I θ̇`. + +-/ + +/-- The gradient of the Lagrangian of the simple pendulum in the angle is minus the gradient of + the potential energy, `-m g ℓ sin θ` times the unit vector of the angular coordinate. -/ +lemma gradient_lagrangian_position_eq (t : Time) (x v : EuclideanSpace ℝ (Fin 1)) : + gradient (fun x => S.lagrangian t x v) x = + -((S.m * S.g * S.ℓ * Real.sin (x 0)) • EuclideanSpace.single 0 1) := by + have h : (fun y : EuclideanSpace ℝ (Fin 1) => S.lagrangian t y v) = + fun y => (-1 : ℝ) * S.potentialEnergy y + (1 / (2 : ℝ)) * S.inertia * ⟪v, v⟫_ℝ := by + funext y + rw [lagrangian] + ring + rw [h, gradient_add_const, gradient_const_mul _ (S.differentiable_potentialEnergy x), + gradient_potentialEnergy] + module + +/-- The gradient of the Lagrangian of the simple pendulum in the angular velocity is the angular + momentum `I θ̇` about the pivot. -/ +lemma gradient_lagrangian_velocity_eq (t : Time) (x v : EuclideanSpace ℝ (Fin 1)) : + gradient (S.lagrangian t x) v = S.inertia • v := by + have h : S.lagrangian t x = fun y : EuclideanSpace ℝ (Fin 1) => + ((1 / (2 : ℝ)) * S.inertia) * ⟪y, y⟫_ℝ + -S.potentialEnergy x := by + funext y + rw [lagrangian] + ring + rw [h, gradient_add_const, gradient_const_mul_inner_self] + module + +/-! + +## E. The torque and the equation of motion + +Gravity exerts on the bob a torque `-m g ℓ sin θ` about the pivot, the generalized force conjugate +to the angle, and the equation of motion balances it against the rate of change `I θ̈` of the +angular momentum. + +We take that pointwise relation as the definition of the equation of motion, rather than the +vanishing of the variational derivative of the action, which is how the harmonic oscillator defines +its own. The reason is that the variational derivative is defined to be `0` whenever no variational +gradient exists, so its vanishing holds vacuously for every lift of the angle too rough to admit +one; it says what it is meant to say only under a smoothness assumption. The pointwise equation is +totalized too — `∂ₜ` is `fderiv`, which is `0` off differentiability — but its totalization cannot +make the equation vacuously true: both sides remain genuine, and generally unequal, functions of +time. A rough lift can still satisfy the equation accidentally — a discontinuous lift hopping +between equilibrium angles solves it, as section E.3 explains — which is why the notion of a +solution, `IsSolution`, demands smoothness as well. It is also the form in which the equation of +motion is solved and used. The two agree for smooth lifts, by +`equationOfMotion_iff_gradLagrangian_zero`, proved in a subsequent contribution; the present +module goes as far as `gradLagrangian_eq_torque`, from which that equivalence is one rearrangement +away. + +-/ + +/-! + +### E.1. The torque + +The pendulum is conservative, so the generalized force conjugate to the angle is minus the +gradient of the potential energy. It is a torque about the pivot rather than a force, the angle +being the coordinate; this is why it is `m g ℓ sin θ` and not `m g sin θ`. + +-/ + +/-- The generalized force of the simple pendulum conjugate to the angle, that is the torque about + the pivot, is minus the gradient of the potential energy, `τ = -∂V/∂θ`. -/ +noncomputable def torque (x : EuclideanSpace ℝ (Fin 1)) : EuclideanSpace ℝ (Fin 1) := + -gradient S.potentialEnergy x + +/-- The torque of the simple pendulum is `-m g ℓ sin θ` times the unit vector of the angular + coordinate. It is restoring near the bottom of the swing: for `|θ| < π` it opposes the + displacement, and it vanishes both at the bottom and at the inverted position. -/ +lemma torque_eq (x : EuclideanSpace ℝ (Fin 1)) : + S.torque x = -((S.m * S.g * S.ℓ * Real.sin (x 0)) • EuclideanSpace.single 0 1) := by + rw [torque, gradient_potentialEnergy] + +/-- The single component of the torque of the simple pendulum is `-m g ℓ sin θ`. -/ +lemma torque_apply (x : EuclideanSpace ℝ (Fin 1)) : + S.torque x 0 = -(S.m * S.g * S.ℓ * Real.sin (x 0)) := by + rw [torque_eq] + simp + +/-! + +### E.2. The equation of motion + +The equation of motion of the simple pendulum equates the rate of change of the angular momentum +about the pivot with the torque of gravity, at every instant. + +-/ + +/-- The equation of motion of the simple pendulum: at every instant the rate of change `I θ̈` of + the angular momentum about the pivot equals the torque `τ(θ)` of gravity. + + This pointwise relation, and not the vanishing of the variational derivative of the action, is + the definition of the equation of motion here; see the discussion in section E. For a smooth + lift of the angle the two agree, by `equationOfMotion_iff_gradLagrangian_zero` in a subsequent + contribution. -/ +def EquationOfMotion (θ : Time → EuclideanSpace ℝ (Fin 1)) : Prop := + ∀ t, S.inertia • ∂ₜ (∂ₜ θ) t = S.torque (θ t) + +/-- The equation of motion of the simple pendulum with all of its terms on one side: at every + instant the rate of change `I θ̈` of the angular momentum plus the gradient of the potential + energy vanishes. This is the rotational form of Newton's second law, in the shape in which + `DampedHarmonicOscillator` states its own; the sum on the left is exactly the combination that + `energy_deriv` pairs with the velocity `∂ₜ θ`. -/ +lemma equationOfMotion_iff_newtons_2nd_law (θ : Time → EuclideanSpace ℝ (Fin 1)) : + S.EquationOfMotion θ ↔ + ∀ t, S.inertia • ∂ₜ (∂ₜ θ) t + gradient S.potentialEnergy (θ t) = 0 := by + simp only [EquationOfMotion, torque, eq_neg_iff_add_eq_zero] + +/-! + +### E.3. Smooth solutions + +A solution of the pendulum is a smooth lift satisfying the equation of motion. Smoothness is part +of the definition because the bare pointwise equation, being totalized, admits unphysical +solutions: a lift jumping between the equilibrium angles `0` and `π` has zero torque everywhere, +and — being locally constant wherever it is differentiable at all — it has `∂ₜ θ`, and hence +`∂ₜ (∂ₜ θ)`, identically zero, so it satisfies the equation even when it is nowhere continuous. +Demanding smoothness excludes such junk, and is the regularity under which the variational +description of the motion agrees with the pointwise one. + +-/ + +/-- A solution of the simple pendulum is a smooth lift of the angle satisfying the equation of + motion. -/ +def IsSolution (θ : Time → EuclideanSpace ℝ (Fin 1)) : Prop := + ContDiff ℝ ∞ θ ∧ S.EquationOfMotion θ + +/-- A solution of the simple pendulum is smooth. -/ +lemma IsSolution.contDiff {S : SimplePendulum} {θ : Time → EuclideanSpace ℝ (Fin 1)} + (h : S.IsSolution θ) : ContDiff ℝ ∞ θ := h.1 + +/-- A solution of the simple pendulum satisfies the equation of motion. -/ +lemma IsSolution.equationOfMotion {S : SimplePendulum} {θ : Time → EuclideanSpace ℝ (Fin 1)} + (h : S.IsSolution θ) : S.EquationOfMotion θ := h.2 + +/-! + +### E.4. The scalar equation and independence of the mass + +The angle is a single number, so the vector equation of motion is equivalent to the scalar +equation obtained by reading off its one component. Dividing that component by the moment of +inertia, using `ω_sq_mul_inertia`, cancels the mass and leaves `θ̈ + ω² sin θ = 0`: two pendulums +with the same `ω = √(g/ℓ)` have exactly the same angular motions, whatever their masses. + +-/ + +/-- The equation of motion of the simple pendulum in scalar form, `θ̈ + ω² sin θ = 0`. The mass + has cancelled: only the angular frequency `ω = √(g/ℓ)` survives. -/ +lemma equationOfMotion_iff_scalar (θ : Time → EuclideanSpace ℝ (Fin 1)) : + S.EquationOfMotion θ ↔ ∀ t, ∂ₜ (∂ₜ θ) t 0 + S.ω ^ 2 * Real.sin (θ t 0) = 0 := by + simp only [EquationOfMotion] + refine forall_congr' fun t => ?_ + have hcomp : (S.inertia • ∂ₜ (∂ₜ θ) t = S.torque (θ t)) ↔ + S.inertia * ∂ₜ (∂ₜ θ) t 0 = -(S.m * S.g * S.ℓ * Real.sin (θ t 0)) := by + rw [← S.torque_apply (θ t)] + constructor + · intro h + simpa using congrArg (fun y : EuclideanSpace ℝ (Fin 1) => y 0) h + · intro h + ext i + fin_cases i + simpa using h + rw [hcomp, ← S.ω_sq_mul_inertia] + constructor + · intro h + have h' : S.inertia * (∂ₜ (∂ₜ θ) t 0 + S.ω ^ 2 * Real.sin (θ t 0)) = 0 := by + linear_combination h + exact (mul_eq_zero.mp h').resolve_left S.inertia_ne_zero + · intro h + linear_combination S.inertia * h + +/-- Two simple pendulums with the same angular frequency have the same angular equation of + motion, and hence the same angular motions; in particular, changing only the mass does not + affect the angular motion. An equal `ω` still permits different lengths, and so different + trajectories of the bob in space. -/ +lemma equationOfMotion_iff_of_eq_ω (S' : SimplePendulum) (h : S'.ω = S.ω) + (θ : Time → EuclideanSpace ℝ (Fin 1)) : + S'.EquationOfMotion θ ↔ S.EquationOfMotion θ := by + rw [S'.equationOfMotion_iff_scalar, S.equationOfMotion_iff_scalar, h] + +/-! + +## F. The variational derivative of the action + +The action of the simple pendulum is the time integral of the Lagrangian along a lift of the +angle. Its variational derivative is computed here, in two steps: it is the Euler–Lagrange +operator of the Lagrangian, and that operator is the torque minus the rate of change of the +angular momentum. + +-/ + +/-! + +### F.1. The definition of the variational derivative + +The variational derivative is that of Physlib's variational calculus, applied to the action of the +pendulum. Recall that it is defined to be `0` when no variational gradient exists, so the lemmas +below are stated for smooth lifts of the angle. + +-/ + +/-- The variational derivative of the action of the simple pendulum, the action being the time + integral of the Lagrangian along a lift of the angle. -/ +noncomputable def gradLagrangian (θ : Time → EuclideanSpace ℝ (Fin 1)) : + Time → EuclideanSpace ℝ (Fin 1) := + (δ (q':=θ), ∫ t, S.lagrangian t (q' t) (fderiv ℝ q' t 1)) + +/-! + +### F.2. Equality with the Euler–Lagrange operator + +For a smooth lift of the angle the variational derivative of the action is the Euler–Lagrange +operator of the Lagrangian, by the general theorem `euler_lagrange_varGradient`; the hypotheses +of that theorem are the smoothness of the lift and `contDiff_lagrangian`. + +-/ + +/-- For a smooth lift of the angle the variational derivative of the action of the simple + pendulum is the Euler–Lagrange operator of its Lagrangian. -/ +lemma gradLagrangian_eq_eulerLagrangeOp (θ : Time → EuclideanSpace ℝ (Fin 1)) + (hθ : ContDiff ℝ ∞ θ) : + S.gradLagrangian θ = eulerLagrangeOp S.lagrangian θ := by + rw [gradLagrangian, euler_lagrange_varGradient _ _ hθ (S.contDiff_lagrangian _)] + +/-! + +### F.3. The variational derivative in terms of the torque + +Evaluating the Euler–Lagrange operator with the gradients of section D.3 gives the variational +derivative as the torque minus the rate of change of the angular momentum. Its vanishing is +therefore the equation of motion of section E; that equivalence, +`equationOfMotion_iff_gradLagrangian_zero`, is proved in a subsequent contribution, together +with energy conservation, so that this module carries the model of the pendulum alone. + +-/ + +/-- For a smooth lift of the angle the variational derivative of the action of the simple + pendulum is the torque minus the rate of change `I θ̈` of the angular momentum. -/ +lemma gradLagrangian_eq_torque (θ : Time → EuclideanSpace ℝ (Fin 1)) (hθ : ContDiff ℝ ∞ θ) : + S.gradLagrangian θ = fun t => S.torque (θ t) - S.inertia • ∂ₜ (∂ₜ θ) t := by + funext t + rw [S.gradLagrangian_eq_eulerLagrangeOp θ hθ, eulerLagrangeOp] + simp [S.gradient_lagrangian_position_eq, S.gradient_lagrangian_velocity_eq, S.torque_eq, + Time.deriv_smul _ S.inertia (deriv_differentiable_of_contDiff θ hθ)] + +end SimplePendulum + +end ClassicalMechanics + +end From 4a4de62f470bb78bb9b07b71c7cc0f07fd615f68 Mon Sep 17 00:00:00 2001 From: Tom Diem Date: Mon, 24 Aug 2026 11:28:50 +0200 Subject: [PATCH 10/20] The Jordan product on observables (#1559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(OperatorAlgebra): add Jordan structure on observables Co-authored-by: Claude Opus 4.8 * refactor(OperatorAlgebra): express Jordan product through realPart Co-authored-by: Claude Opus 4.8 * fix(Jordan): satisfy documentation and simp linters Co-authored-by: Claude Opus 4.8 * refactor(OperatorAlgebra): group Jordan observables Co-authored-by: Claude Opus 4.8 * refactor(Jordan): use OperatorAlgebra assumptions * fix(Jordan): address review comments - Drop @[simp] from coe_jordan so it doesn't force-unfold the Jordan product coercion everywhere; it's still available to invoke explicitly, which is all the existing proofs need. - Turn JordanObservable from an abbrev into a def so that the NonUnitalNonAssocCommRing instance built on it isn't picked up by typeclass search on Observable A itself (Observable has no canonical multiplication). Forward the AddCommGroup and Module ℝ instances explicitly since def blocks the automatic inheritance abbrev gave for free. --------- Co-authored-by: Claude Opus 4.8 --- Physlib.lean | 1 + .../OperatorAlgebra/Observables/Jordan.lean | 138 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 Physlib/QuantumMechanics/OperatorAlgebra/Observables/Jordan.lean diff --git a/Physlib.lean b/Physlib.lean index 26328bc2fc..debcb31f8e 100644 --- a/Physlib.lean +++ b/Physlib.lean @@ -339,6 +339,7 @@ public import Physlib.QuantumMechanics.Hydrogen.Basic public import Physlib.QuantumMechanics.Hydrogen.LaplaceRungeLenzVector public import Physlib.QuantumMechanics.InfiniteSquareWell.Basic public import Physlib.QuantumMechanics.OperatorAlgebra.Basic +public import Physlib.QuantumMechanics.OperatorAlgebra.Observables.Jordan public import Physlib.QuantumMechanics.OperatorAlgebra.Observables.Lie public import Physlib.QuantumMechanics.Operators.AngularMomentum public import Physlib.QuantumMechanics.Operators.Commutation diff --git a/Physlib/QuantumMechanics/OperatorAlgebra/Observables/Jordan.lean b/Physlib/QuantumMechanics/OperatorAlgebra/Observables/Jordan.lean new file mode 100644 index 0000000000..6f5013ee83 --- /dev/null +++ b/Physlib/QuantumMechanics/OperatorAlgebra/Observables/Jordan.lean @@ -0,0 +1,138 @@ +/- +Copyright (c) 2026 Tom Ole Diem. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Tom Ole Diem +-/ +module + +public import Physlib.QuantumMechanics.OperatorAlgebra.Basic +public import Physlib.Meta.TODO.Basic +public import Mathlib.Algebra.Jordan.Basic +public import Mathlib.LinearAlgebra.Complex.Module + +/-! + +# Jordan structure on observables + +The observables of a complex C⋆-algebra carry the symmetrized product + + a ⊙ b = 1/2 (ab + ba), + +equivalently the real part of their algebra product. + +This product is commutative and satisfies the Jordan identity. The type +`JordanObservable A` equips the real vector space of observables with this +product as its multiplication, giving a commutative Jordan algebra. + +-/ + +TODO "Investigate `https://github.com/Cobord/JordanAlgebra/` and determine + which general Jordan-algebra results are relevant for quantum mechanics." + +@[expose] public section + +namespace OperatorAlgebra + +variable {A : Type*} [OperatorAlgebra A] + +namespace Observable + +/-- The symmetrized product of two observables. -/ +noncomputable def jordan (a b : Observable A) : Observable A := + realPart ((a : A) * (b : A)) + +/-- The Jordan product on observables. -/ +scoped[OperatorAlgebra] infixl:70 " ⊙ " => Observable.jordan + +lemma coe_jordan (a b : Observable A) : + (a ⊙ b : A) = (2⁻¹ : ℝ) • ((a : A) * b + (b : A) * a) := by + change (↑(realPart ((a : A) * (b : A))) : A) = + (2⁻¹ : ℝ) • ((a : A) * b + (b : A) * a) + rw [realPart_apply_coe, star_mul, a.property.star_eq, b.property.star_eq] + +lemma jordan_comm (a b : Observable A) : + a ⊙ b = b ⊙ a := by + apply Subtype.ext + simp [coe_jordan, add_comm] + +lemma jordan_self (a : Observable A) : + (a ⊙ a : A) = (a : A) * a := by + rw [coe_jordan] + module + +lemma add_jordan (a b c : Observable A) : + (a + b) ⊙ c = a ⊙ c + b ⊙ c := by + change realPart (((a + b : Observable A) : A) * (c : A)) = + realPart ((a : A) * (c : A)) + realPart ((b : A) * (c : A)) + rw [AddSubgroup.coe_add, add_mul, map_add] + +lemma jordan_add (a b c : Observable A) : + a ⊙ (b + c) = a ⊙ b + a ⊙ c := by + change realPart ((a : A) * ((b + c : Observable A) : A)) = + realPart ((a : A) * (b : A)) + realPart ((a : A) * (c : A)) + rw [AddSubgroup.coe_add, mul_add, map_add] + +lemma jordan_smul (t : ℝ) (a b : Observable A) : + a ⊙ (t • b) = t • (a ⊙ b) := by + change realPart ((a : A) * ((t • b : Observable A) : A)) = + t • realPart ((a : A) * (b : A)) + rw [selfAdjoint.val_smul, mul_smul_comm] + exact map_smul (realPart : A →ₗ[ℝ] Observable A) t ((a : A) * (b : A)) + +lemma jordan_identity (a b : Observable A) : + (a ⊙ b) ⊙ (a ⊙ a) = a ⊙ (b ⊙ (a ⊙ a)) := by + apply Subtype.ext + simp only [coe_jordan, smul_add] + norm_num [← smul_add] + noncomm_ring + +end Observable + +/-! ## Jordan observables + +`JordanObservable A` is the same underlying real vector space equipped with the +symmetrized product as multiplication. +-/ + +/-- Observables equipped with their Jordan multiplication. + +This is a type synonym for `Observable A`, kept a `def` (rather than an `abbrev`) so that the +`NonUnitalNonAssocCommRing` structure defined below on `JordanObservable A` is not silently +inherited by `Observable A` itself, which has no canonical multiplication of its own. -/ +noncomputable def JordanObservable (A : Type*) [OperatorAlgebra A] := + Observable A + +namespace JordanObservable + +variable {A : Type*} [OperatorAlgebra A] + +noncomputable instance instAddCommGroup : AddCommGroup (JordanObservable A) := + inferInstanceAs (AddCommGroup (Observable A)) + +noncomputable instance instModule : Module ℝ (JordanObservable A) := + inferInstanceAs (Module ℝ (Observable A)) + +open scoped Observable + +noncomputable instance instNonUnitalNonAssocCommRing : + NonUnitalNonAssocCommRing (JordanObservable A) where + mul a b := a ⊙ b + mul_comm a b := Observable.jordan_comm a b + left_distrib a b c := Observable.jordan_add a b c + right_distrib a b c := Observable.add_jordan a b c + zero_mul a := by + change (0 : Observable A) ⊙ a = 0 + simp [Observable.jordan] + mul_zero a := by + change a ⊙ (0 : Observable A) = 0 + simp [Observable.jordan] + +noncomputable instance instIsCommJordan : + IsCommJordan (JordanObservable A) where + lmul_comm_rmul_rmul a b := by + change (a ⊙ b) ⊙ (a ⊙ a) = a ⊙ (b ⊙ (a ⊙ a)) + exact Observable.jordan_identity a b + +end JordanObservable + +end OperatorAlgebra From 8f9a40bd29cd536e21150b40fc86fa2355cab196 Mon Sep 17 00:00:00 2001 From: Tom Diem Date: Tue, 25 Aug 2026 06:15:08 +0200 Subject: [PATCH 11/20] The static theorem for reversible dynamics on Hilbert spaces (#1556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(OperatorAlgebra): characterize reversible Hilbert-space dynamics Co-authored-by: Claude Opus 4.8 * docs(OperatorAlgebra): note Hamiltonian quotient TODO Co-authored-by: Claude Opus 4.8 * revert(OperatorAlgebra): remove local Hamiltonian TODO Co-authored-by: Claude Opus 4.8 * feat(OperatorAlgebra): structure bounded operators Co-authored-by: Claude Opus 4.8 * refactor(OperatorAlgebra): stack automorphism on Basic * refactor(OperatorAlgebra): separate Hilbert-space layer * refactor(OperatorAlgebra): bundle Hilbert-space assumptions * fix: sort Physlib imports * refactor(HilbertSpace): drop the bundling class per review Remove OperatorAlgebra.HilbertSpace and use the flat instance list [NormedAddCommGroup H] [InnerProductSpace ℂ H] [CompleteSpace H] directly, per jstoobysmith's suggestion: bundling classes over already-existing instance conjunctions risks instance diamonds and hides Mathlib lemmas keyed on the flat instances, for no real benefit here. --------- Co-authored-by: Claude Opus 4.8 --- Physlib.lean | 2 + .../OperatorAlgebra/Basic.lean | 38 ++--------- .../Dynamics/Automorphism.lean | 68 +++++++++++++++++++ .../OperatorAlgebra/HilbertSpace.lean | 39 +++++++++++ 4 files changed, 113 insertions(+), 34 deletions(-) create mode 100644 Physlib/QuantumMechanics/OperatorAlgebra/Dynamics/Automorphism.lean create mode 100644 Physlib/QuantumMechanics/OperatorAlgebra/HilbertSpace.lean diff --git a/Physlib.lean b/Physlib.lean index debcb31f8e..a256cbe3fc 100644 --- a/Physlib.lean +++ b/Physlib.lean @@ -339,6 +339,8 @@ public import Physlib.QuantumMechanics.Hydrogen.Basic public import Physlib.QuantumMechanics.Hydrogen.LaplaceRungeLenzVector public import Physlib.QuantumMechanics.InfiniteSquareWell.Basic public import Physlib.QuantumMechanics.OperatorAlgebra.Basic +public import Physlib.QuantumMechanics.OperatorAlgebra.Dynamics.Automorphism +public import Physlib.QuantumMechanics.OperatorAlgebra.HilbertSpace public import Physlib.QuantumMechanics.OperatorAlgebra.Observables.Jordan public import Physlib.QuantumMechanics.OperatorAlgebra.Observables.Lie public import Physlib.QuantumMechanics.Operators.AngularMomentum diff --git a/Physlib/QuantumMechanics/OperatorAlgebra/Basic.lean b/Physlib/QuantumMechanics/OperatorAlgebra/Basic.lean index a14e3a7be0..454595af1b 100644 --- a/Physlib/QuantumMechanics/OperatorAlgebra/Basic.lean +++ b/Physlib/QuantumMechanics/OperatorAlgebra/Basic.lean @@ -6,7 +6,6 @@ Authors: Tom Ole Diem module public import Mathlib.Analysis.CStarAlgebra.CompletelyPositiveMap -public import Mathlib.Analysis.InnerProductSpace.StarOrder /-! @@ -48,18 +47,15 @@ variable {A : Type*} [OperatorAlgebra A] /-- An observable is a self-adjoint element of `A`: position, momentum, energy, spin, ... . Self-adjointness is exactly what makes an element a *measurable* quantity — it is what forces its spectrum, the possible measurement outcomes, to be real. -/ -noncomputable abbrev Observable (A : Type*) [CStarAlgebra A] := - selfAdjoint A +noncomputable abbrev Observable (A : Type*) [OperatorAlgebra A] := selfAdjoint A /-- A positive element of `A`: an observable whose measurement outcomes are all `≥ 0`. Positivity is what gives observables a meaningful order (`a ≤ b` meaning `b - a` is positive). -/ -abbrev PositiveElement (A : Type*) [OperatorAlgebra A] := - {a : Observable A // 0 ≤ (a : A)} +abbrev PositiveElement (A : Type*) [OperatorAlgebra A] := {a : Observable A // 0 ≤ (a : A)} /-- An effect is an observable between zero and the identity, representing a yes/no measurement outcome. -/ -abbrev Effect (A : Type*) [OperatorAlgebra A] := - Set.Icc (0 : Observable A) 1 +abbrev Effect (A : Type*) [OperatorAlgebra A] := Set.Icc (0 : Observable A) 1 /-- A finite POVM on `A`: the most general notion of a measurement with outcomes in `X`, generalizing a single yes/no `Effect` to several possible outcomes. -/ @@ -71,8 +67,7 @@ structure POVM (A : Type*) [OperatorAlgebra A] (X : Type*) [Fintype X] where /-- A unitary element of `A`: implements a reversible transformation of the system — a symmetry, or time evolution under a Hamiltonian — acting on observables by conjugation, `a ↦ U a U⋆`. -/ -noncomputable abbrev Unitary (A : Type*) [CStarAlgebra A] := - unitary A +noncomputable abbrev Unitary (A : Type*) [OperatorAlgebra A] := unitary A /-- A state on `A`: a positive complex-linear functional normalized by `ω 1 = 1`. `ω a` is the expected outcome of measuring observable `a` in this state — a state records everything that can @@ -90,29 +85,4 @@ abbrev Channel (A₁ A₂ : Type*) [OperatorAlgebra A₁] [OperatorAlgebra A₂] end ObservableAlgebra -/-! -## Hilbert-space representations - -The abstract observable algebra need not initially be presented as operators on -a Hilbert space. - -A concrete realization is a unital ⋆-representation into the C⋆-algebra of -bounded operators on a complex Hilbert space. - -This is also the target of the GNS construction associated with a state. --/ - -section Representation - -variable {A : Type*} {H : Type*} [CStarAlgebra A] [NormedAddCommGroup H] [InnerProductSpace ℂ H] - [CompleteSpace H] - -/-- A Hilbert-space representation of `A`: a unital ⋆-homomorphism from `A` into the algebra of -bounded operators on the Hilbert space `H`. -/ -abbrev Representation (A : Type*) (H : Type*) [CStarAlgebra A] [NormedAddCommGroup H] - [InnerProductSpace ℂ H] [CompleteSpace H] := - A →⋆ₐ[ℂ] (H →L[ℂ] H) - -end Representation - end OperatorAlgebra diff --git a/Physlib/QuantumMechanics/OperatorAlgebra/Dynamics/Automorphism.lean b/Physlib/QuantumMechanics/OperatorAlgebra/Dynamics/Automorphism.lean new file mode 100644 index 0000000000..42a1f4cf81 --- /dev/null +++ b/Physlib/QuantumMechanics/OperatorAlgebra/Dynamics/Automorphism.lean @@ -0,0 +1,68 @@ +/- +Copyright (c) 2026 Tom Ole Diem. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Tom Ole Diem +-/ +module + +public import Physlib.QuantumMechanics.OperatorAlgebra.HilbertSpace +public import Mathlib.Analysis.Normed.Operator.ContinuousAlgEquiv +public import Mathlib.Analysis.CStarAlgebra.Hom + +/-! +# Automorphisms of the bounded operators + +Reversible transformations of a quantum system act on its observable algebra by +⋆-automorphisms. + +For a complex Hilbert space `H`, every ⋆-automorphism of `B(H)` is implemented by +unitary conjugation: + `A ↦ U A U⋆`. + +Two unitaries implement the same transformation exactly when they differ by a +scalar phase. Consequently, + `Aut⋆(B(H)) ≅ U(H) / U(1) ≅ PU(H)`, +the projective unitary group. + +For Hamiltonian dynamics, this projective ambiguity corresponds to +the freedom to shift a Hamiltonian by a scalar multiple of the identity. +-/ + +@[expose] public section + +namespace OperatorAlgebra + +variable {H : Type*} [NormedAddCommGroup H] [InnerProductSpace ℂ H] [CompleteSpace H] + +/-- Every ⋆-automorphism of `B(H)` is implemented by unitary conjugation. -/ +lemma conjStarAlgAut_surjective : + Function.Surjective (Unitary.conjStarAlgAut ℂ (B(H))) := by + intro φ + obtain ⟨U, hU⟩ := + φ.eq_linearIsometryEquivConjStarAlgEquiv + (NonUnitalStarAlgHom.isometry φ φ.injective).continuous + refine ⟨Unitary.linearIsometryEquiv.symm U, ?_⟩ + rw [Unitary.conjStarAlgAut_symm_unitaryLinearIsometryEquiv] + exact hU.symm + +/-- Two unitaries implement the same ⋆-automorphism of `B(H)` exactly when they differ by a scalar +phase. -/ +lemma conjStarAlgAut_eq_iff (u v : Unitary (B(H))) : + Unitary.conjStarAlgAut ℂ (B(H)) u = + Unitary.conjStarAlgAut ℂ (B(H)) v ↔ + ∃ c : unitary ℂ, u = c • v := + Unitary.conjStarAlgAut_ext_iff' u v + +/-- The projective unitary group of `H`, obtained by quotienting out scalar phases. -/ +def ProjectiveUnitary (H : Type*) [NormedAddCommGroup H] [InnerProductSpace ℂ H] + [CompleteSpace H] := + Unitary (B(H)) ⧸ MonoidHom.ker (Unitary.conjStarAlgAut ℂ (B(H))) + +noncomputable instance : Group (ProjectiveUnitary H) := QuotientGroup.Quotient.group _ + +/-- Reversible transformations of `B(H)` are precisely projective unitaries. -/ +noncomputable def projectiveUnitaryEquivStarAlgAut : + ProjectiveUnitary H ≃* ((B(H)) ≃⋆ₐ[ℂ] (B(H))) := + QuotientGroup.quotientKerEquivOfSurjective _ conjStarAlgAut_surjective + +end OperatorAlgebra diff --git a/Physlib/QuantumMechanics/OperatorAlgebra/HilbertSpace.lean b/Physlib/QuantumMechanics/OperatorAlgebra/HilbertSpace.lean new file mode 100644 index 0000000000..4dfd8e4319 --- /dev/null +++ b/Physlib/QuantumMechanics/OperatorAlgebra/HilbertSpace.lean @@ -0,0 +1,39 @@ +/- +Copyright (c) 2026 Tom Ole Diem. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Tom Ole Diem +-/ +module + +public import Physlib.QuantumMechanics.OperatorAlgebra.Basic +public import Mathlib.Analysis.InnerProductSpace.StarOrder + +/-! +# Bounded operators on Hilbert space + +This file connects the abstract operator-algebraic quantum-mechanics API with the concrete +C⋆-algebra of bounded operators on a complex Hilbert space. The C⋆-algebra, Loewner order, and +ordered-star-ring instances for bounded operators are supplied by Mathlib. +-/ + +@[expose] public section + +namespace OperatorAlgebra + +/-- Bounded operators on a complex Hilbert space, written in the usual physics notation. -/ +notation "B(" H ")" => H →L[ℂ] H + +variable {H : Type*} [NormedAddCommGroup H] [InnerProductSpace ℂ H] [CompleteSpace H] + +/-- Bounded operators form an `OperatorAlgebra` using Mathlib's native Hilbert-space instances. -/ +noncomputable instance instOperatorAlgebraBoundedOperators : OperatorAlgebra B(H) := {} + +section Representation + +/-- A Hilbert-space representation of `A` as a unital ⋆-homomorphism into `B(H)`. -/ +abbrev Representation (A H : Type*) [OperatorAlgebra A] [NormedAddCommGroup H] + [InnerProductSpace ℂ H] [CompleteSpace H] := A →⋆ₐ[ℂ] B(H) + +end Representation + +end OperatorAlgebra From 6be0ec082bd40aaee313668b39351ec93db11ca4 Mon Sep 17 00:00:00 2001 From: aadarsh agarwal Date: Tue, 25 Aug 2026 02:44:44 -0500 Subject: [PATCH 12/20] feat(Mathematics): Real.completeEllipticK, Legendre's complete elliptic integral of the first kind (#1573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(Mathematics): Real.completeEllipticK, its integrability and positivity for m < 1 - Real.completeEllipticK m := ∫ φ in 0..π/2, (1 - m sin² φ)^(-1/2), the parameter convention (Abramowitz–Stegun 17.3.1), with an honest account of the junk values for m ≥ 1 - completeEllipticK_integrand_pos, continuous_completeEllipticK_integrand, completeEllipticK_intervalIntegrable (all under m < 1) - completeEllipticK_zero : K 0 = π/2 (simp); completeEllipticK_pos for m < 1 - registered in Physlib.lean Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 * feat(Mathematics): completeEllipticK is monotone and continuous on (-∞, 1) - completeEllipticK_mono : m₁ ≤ m₂ < 1 → K m₁ ≤ K m₂, from the pointwise monotonicity of the integrand in the parameter (intervalIntegral.integral_mono_on, rpow_le_rpow_of_nonpos) - pi_div_two_le_completeEllipticK : 0 ≤ m < 1 → π/2 ≤ K m, from monotonicity and K 0 = π/2 - completeEllipticK_continuousOn : ContinuousOn completeEllipticK (Set.Iio 1), via intervalIntegral.continuous_parametric_intervalIntegral_of_continuous' with the parameter restricted to the subtype Set.Iio 1 (continuousOn_iff_continuous_domRestrict) - completeEllipticK_continuousAt_zero : ContinuousAt completeEllipticK 0 - sections C–D, key results and table of contents updated; no new imports (DominatedConvergence is already public-imported through Integrals.Basic) Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 * fix(Mathematics): review fixes for the complete elliptic integral module - correct the m ≥ 1 documentation: the Lean value for m > 1 is the finite positive integral over [0, arcsin (1/√m)], i.e. K(1/m)/√m (DLMF 19.7(ii)), not an unrelated number; K(1) = ∞ vs the totalized 0 - rename completeEllipticK_integrand_pos → completeEllipticK_radicand_pos (it bounds the radicand), and align the two integrand lemmas to the subject-first Mathlib scheme - drop the redundant Integrals.Basic import; import DominatedConvergence explicitly for the parametric-continuity lemma - document the rpow-vs-1/√ choice; golf the radicand positivity proof Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 * fix(Mathematics): Mathlib-convention names and API additions for completeEllipticK - rename to head-symbol-first: continuous_completeEllipticK_integrand, intervalIntegrable_completeEllipticK_integrand, continuousOn_completeEllipticK, continuousAt_completeEllipticK_zero - drop the dead IntervalIntegral.Basic import (DominatedConvergence's closure has it) - @[pp_nodot] on the definition; @[gcongr] on completeEllipticK_mono - add completeEllipticK_eq, monotoneOn_completeEllipticK, completeEllipticK_strictMono, completeEllipticK_le (the upper bound (π/2)(1 - m)^(-1/2)), and the general continuousAt_completeEllipticK with the zero case derived from it - docs: DLMF §19.7(ii) and L&L §11 Problem 1 in the references; m = 1 sentence tightened Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 * refactor(Mathematics): move the sine-square bounds to Trigonometry.SinSq and motivate completeEllipticK physically - Real.sin_sq_lt_one and Real.sin_half_sq_lt_one move from EllipticIntegral.lean to the new Physlib/Mathematics/Trigonometry/SinSq.lean, registered in Physlib.lean, per review: they are elementary facts about the sine unrelated to the elliptic integral, and nothing in EllipticIntegral.lean uses them, so the new file is not imported there - the §i Overview of EllipticIntegral.lean gains a paragraph on where K enters physics and why Physlib defines it, per review: the exact pendulum period 4 √(ℓ/g) K(sin² (θ₀/2)) (Landau & Lifshitz §11, Problem 1), its consumer SimplePendulum.PeriodFormula, and the loop field, ring potential and ellipse arc length where the complete integrals of the first and second kind appear more broadly Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 --------- Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 --- Physlib.lean | 2 + .../SpecialFunctions/EllipticIntegral.lean | 251 ++++++++++++++++++ Physlib/Mathematics/Trigonometry/SinSq.lean | 69 +++++ 3 files changed, 322 insertions(+) create mode 100644 Physlib/Mathematics/SpecialFunctions/EllipticIntegral.lean create mode 100644 Physlib/Mathematics/Trigonometry/SinSq.lean diff --git a/Physlib.lean b/Physlib.lean index a256cbe3fc..6ee5884102 100644 --- a/Physlib.lean +++ b/Physlib.lean @@ -140,7 +140,9 @@ public import Physlib.Mathematics.RatComplexNum public import Physlib.Mathematics.Resolvent public import Physlib.Mathematics.SO3.Basic public import Physlib.Mathematics.SchurTriangulation +public import Physlib.Mathematics.SpecialFunctions.EllipticIntegral public import Physlib.Mathematics.SpecialFunctions.PhysHermite +public import Physlib.Mathematics.Trigonometry.SinSq public import Physlib.Mathematics.Trigonometry.Tanh public import Physlib.Mathematics.VariationalCalculus.Basic public import Physlib.Mathematics.VariationalCalculus.HasVarAdjDeriv diff --git a/Physlib/Mathematics/SpecialFunctions/EllipticIntegral.lean b/Physlib/Mathematics/SpecialFunctions/EllipticIntegral.lean new file mode 100644 index 0000000000..a27a171893 --- /dev/null +++ b/Physlib/Mathematics/SpecialFunctions/EllipticIntegral.lean @@ -0,0 +1,251 @@ +/- +Copyright (c) 2026 Aadarsh Agarwal. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Aadarsh Agarwal +-/ +module + +public import Mathlib.MeasureTheory.Integral.DominatedConvergence +/-! + +# The complete elliptic integral of the first kind + +This file may eventually be upstreamed to Mathlib. + +## i. Overview + +Legendre's complete elliptic integral of the first kind is, in the parameter convention, + +`K(m) = ∫ φ in 0..π/2, (1 - m sin² φ) ^ (-1/2)` + +(Abramowitz & Stegun 17.3.1). The physics literature more often uses the modulus convention and +writes `K(k)`, as Landau & Lifshitz do, for what is here `completeEllipticK (k ^ 2)`; the two +conventions are related by `m = k²`. Mathlib knows the Weierstrass elliptic function `℘` +(`PeriodPair.weierstrassP`) but has no Legendre-form elliptic integrals; this file defines the +complete integral of the first kind and develops its basic theory on the domain `m < 1`, where +the radicand `1 - m sin² φ` is positive and the integrand continuous. + +The integral enters physics wherever a period or a potential is computed exactly rather than in a +small-parameter expansion. Physlib's use of it so far is the simple pendulum: released from rest +at amplitude `θ₀`, the pendulum has period `4 √(ℓ / g) K(sin² (θ₀ / 2))` (Landau & Lifshitz §11, +Problem 1), the consumer being `Physlib.ClassicalMechanics.Pendulum.SimplePendulum.PeriodFormula`. +More broadly, the complete integrals of the first and second kind give the magnetic field of a +circular current loop and the potential of a uniformly charged ring, and the second kind, `E`, +gives the arc length of an ellipse. This file defines `K` so that such results can be stated. + +For `m ≥ 1` the definition still elaborates, but its value is not Legendre's. At `m = 1` the +integrand is `1 / cos φ`, which is not interval integrable on `[0, π/2]`, so the integral is `0` +by `intervalIntegral.integral_undef`, whereas `K(1) = ∞`. For `m > 1` the radicand is negative on +`(arcsin (1/√m), π/2]`, where the real power at exponent `-(1/2)` of a negative base vanishes +(`Real.rpow_def_of_neg` supplies the factor `cos (-(1 / 2) * π) = 0`), so the Lean value is the +finite positive integral over `[0, arcsin (1/√m)]`; by the reciprocal-modulus transformation this +is `K(1/m) / √m`, the real part of the complex Legendre integral (DLMF §19.7(ii)) — not proved +here. Every lemma of this file about a general parameter therefore carries its domain hypothesis +`m < 1` explicitly. + +## ii. Key results + +- `completeEllipticK` : the complete elliptic integral of the first kind, as a function of + the parameter `m`. +- `completeEllipticK_zero` : `K 0 = π / 2`. +- `completeEllipticK_pos` : for `m < 1` the integral is positive. +- `completeEllipticK_mono` : for `m₁ ≤ m₂ < 1`, `K m₁ ≤ K m₂`. +- `completeEllipticK_strictMono` : for `m₁ < m₂ < 1`, `K m₁ < K m₂`. +- `pi_div_two_le_completeEllipticK` : for `0 ≤ m < 1`, `π / 2 ≤ K m`. +- `completeEllipticK_le` : for `0 ≤ m < 1`, `K m ≤ π / 2 * (1 - m) ^ (-1/2)`. +- `continuousOn_completeEllipticK` : `K` is continuous on `(-∞, 1)`. +- `continuousAt_completeEllipticK` : `K` is continuous at every `m < 1`. + +## iii. Table of contents + +- A. Definition and the integrand +- B. Value at zero and positivity +- C. Monotonicity and bounds in the parameter +- D. Continuity on the domain + +## iv. References + +- M. Abramowitz, I. A. Stegun, Handbook of Mathematical Functions, §17.3 (the parameter + convention, 17.3.1). +- NIST DLMF §19.7(ii) (the reciprocal-modulus transformation). +- Landau & Lifshitz, Mechanics, 3rd ed., §11, Problem 1 (the pendulum period as `K(k)`, modulus + convention). + +-/ + +@[expose] public section + +open MeasureTheory + +namespace Real + +/-! + +## A. Definition and the integrand + +The integral is defined for every real parameter `m`; on the domain `m < 1` the radicand is +positive, so the integrand is continuous and interval integrable. + +-/ + +/-- The complete elliptic integral of the first kind in the parameter convention, +`K(m) = ∫ φ in 0..π/2, (1 - m sin² φ) ^ (-1/2)`. The physics literature often writes `K(k)` +with `m = k²`. The integrand is written as a real power rather than `1 / √(…)` so that +continuity, positivity and monotonicity in `m` come from the `rpow` API (`Continuous.rpow_const`, +`Real.rpow_pos_of_pos`, `Real.rpow_le_rpow_of_nonpos`); the two forms agree by +`Real.sqrt_eq_rpow` and `Real.rpow_neg`. For `m ≥ 1` see the module docstring. -/ +@[pp_nodot] +noncomputable def completeEllipticK (m : ℝ) : ℝ := + ∫ φ in (0 : ℝ)..π / 2, (1 - m * sin φ ^ 2) ^ (-(1 / 2 : ℝ)) + +/-- Unfolding lemma: `completeEllipticK m` is the interval integral of its integrand over +`[0, π/2]`. -/ +lemma completeEllipticK_def (m : ℝ) : + completeEllipticK m = ∫ φ in (0 : ℝ)..π / 2, (1 - m * sin φ ^ 2) ^ (-(1 / 2 : ℝ)) := + rfl + +/-- For `m < 1` the radicand `1 - m sin² φ` of the integrand of `completeEllipticK m` is +positive at every angle `φ`. -/ +lemma completeEllipticK_radicand_pos {m : ℝ} (hm : m < 1) (φ : ℝ) : + 0 < 1 - m * sin φ ^ 2 := by + nlinarith [sq_nonneg (sin φ), sin_sq_le_one φ, + mul_nonneg (sub_nonneg.2 hm.le) (sq_nonneg (sin φ))] + +/-- For `m < 1` the integrand of `completeEllipticK m` is continuous, the real power being +taken at a positive base. -/ +lemma continuous_completeEllipticK_integrand {m : ℝ} (hm : m < 1) : + Continuous fun φ : ℝ => (1 - m * sin φ ^ 2) ^ (-(1 / 2 : ℝ)) := by + refine Continuous.rpow_const ?_ fun φ => Or.inl (completeEllipticK_radicand_pos hm φ).ne' + fun_prop + +/-- For `m < 1` the integrand of `completeEllipticK m` is interval integrable on `[0, π/2]`. -/ +lemma intervalIntegrable_completeEllipticK_integrand {m : ℝ} (hm : m < 1) : + IntervalIntegrable (fun φ : ℝ => (1 - m * sin φ ^ 2) ^ (-(1 / 2 : ℝ))) volume 0 (π / 2) := + (continuous_completeEllipticK_integrand hm).intervalIntegrable 0 (π / 2) + +/-! + +## B. Value at zero and positivity + +At `m = 0` the integrand is the constant `1` and the integral is elementary; for `m < 1` the +integral is positive, being the integral of a positive continuous function. + +-/ + +/-- `K 0 = π / 2`: at parameter zero the integrand of `completeEllipticK` is the constant `1`. -/ +@[simp] +lemma completeEllipticK_zero : completeEllipticK 0 = π / 2 := by + simp [completeEllipticK] + +/-- For `m < 1` the complete elliptic integral `completeEllipticK m` is positive. -/ +lemma completeEllipticK_pos {m : ℝ} (hm : m < 1) : 0 < completeEllipticK m := by + refine intervalIntegral.intervalIntegral_pos_of_pos_on + (intervalIntegrable_completeEllipticK_integrand hm) (fun φ _ => ?_) pi_div_two_pos + exact rpow_pos_of_pos (completeEllipticK_radicand_pos hm φ) _ + +/-! + +## C. Monotonicity and bounds in the parameter + +For fixed `φ` the radicand `1 - m sin² φ` decreases in `m`, so the integrand, a negative power of +the radicand, increases in `m` on the domain; integrating the pointwise inequality over +`[0, π/2]` gives monotonicity of `K`, and since the inequality is strict at `φ = π/2` the +monotonicity is strict. Together with `K 0 = π / 2` this bounds `K` below on `[0, 1)`; bounding +the radicand below by `1 - m` bounds `K` above by `π / 2 * (1 - m) ^ (-1/2)` there. + +-/ + +/-- `completeEllipticK` is monotone on its domain: for `m₁ ≤ m₂ < 1`, `K m₁ ≤ K m₂`. The +integrand is pointwise monotone in the parameter, the radicand being positive for both +parameters. -/ +@[gcongr] +lemma completeEllipticK_mono {m₁ m₂ : ℝ} (h12 : m₁ ≤ m₂) (h2 : m₂ < 1) : + completeEllipticK m₁ ≤ completeEllipticK m₂ := by + have h1 : m₁ < 1 := h12.trans_lt h2 + refine intervalIntegral.integral_mono_on pi_div_two_pos.le + (intervalIntegrable_completeEllipticK_integrand h1) + (intervalIntegrable_completeEllipticK_integrand h2) + fun φ _ => ?_ + exact rpow_le_rpow_of_nonpos (completeEllipticK_radicand_pos h2 φ) + (sub_le_sub_left (mul_le_mul_of_nonneg_right h12 (sq_nonneg _)) 1) (by norm_num) + +/-- `completeEllipticK` is monotone on its domain `(-∞, 1)`, as a `MonotoneOn` statement. -/ +lemma monotoneOn_completeEllipticK : MonotoneOn completeEllipticK (Set.Iio 1) := + fun _ _ _ hm₂ h => completeEllipticK_mono h hm₂ + +/-- `completeEllipticK` is strictly monotone on its domain: for `m₁ < m₂ < 1`, `K m₁ < K m₂`. +The pointwise inequality between the integrands is strict at `φ = π / 2`, where `sin² φ = 1`. -/ +@[gcongr] +lemma completeEllipticK_strictMono {m₁ m₂ : ℝ} (h12 : m₁ < m₂) (h2 : m₂ < 1) : + completeEllipticK m₁ < completeEllipticK m₂ := by + have h1 : m₁ < 1 := h12.trans h2 + refine intervalIntegral.integral_lt_integral_of_continuousOn_of_le_of_exists_lt pi_div_two_pos + (continuous_completeEllipticK_integrand h1).continuousOn + (continuous_completeEllipticK_integrand h2).continuousOn + (fun φ _ => ?_) ⟨π / 2, Set.right_mem_Icc.2 pi_div_two_pos.le, ?_⟩ + · exact rpow_le_rpow_of_nonpos (completeEllipticK_radicand_pos h2 φ) + (sub_le_sub_left (mul_le_mul_of_nonneg_right h12.le (sq_nonneg _)) 1) (by norm_num) + · simp only [sin_pi_div_two, one_pow, mul_one] + exact rpow_lt_rpow_of_neg (by linarith) (by linarith) (by norm_num) + +/-- `completeEllipticK` is strictly increasing on `(-∞, 1)`, as a bundled `StrictMonoOn`. -/ +lemma strictMonoOn_completeEllipticK : StrictMonoOn completeEllipticK (Set.Iio 1) := + fun _ _ _ hm₂ h => completeEllipticK_strictMono h hm₂ + +/-- For `0 ≤ m < 1` the complete elliptic integral `completeEllipticK m` is at least its value +`π / 2` at `m = 0`. -/ +lemma pi_div_two_le_completeEllipticK {m : ℝ} (hm0 : 0 ≤ m) (hm1 : m < 1) : + π / 2 ≤ completeEllipticK m := by + rw [← completeEllipticK_zero] + exact completeEllipticK_mono hm0 hm1 + +/-- For `0 ≤ m < 1` the complete elliptic integral `completeEllipticK m` is at most +`π / 2 * (1 - m) ^ (-1/2)`: the radicand is at least `1 - m`, `sin² φ` being at most `1`, so the +integrand is at most the constant `(1 - m) ^ (-1/2)`. With `pi_div_two_le_completeEllipticK` this +sandwiches `K` on `[0, 1)`; for the pendulum, where `m = sin² (θ₀ / 2)`, it bounds the period by +`T ≤ 2π √(ℓ / g) / cos (θ₀ / 2)`. -/ +lemma completeEllipticK_le {m : ℝ} (hm0 : 0 ≤ m) (hm1 : m < 1) : + completeEllipticK m ≤ π / 2 * (1 - m) ^ (-(1 / 2 : ℝ)) := by + have h : completeEllipticK m ≤ ∫ _ in (0 : ℝ)..π / 2, (1 - m) ^ (-(1 / 2 : ℝ)) := by + refine intervalIntegral.integral_mono_on pi_div_two_pos.le + (intervalIntegrable_completeEllipticK_integrand hm1) intervalIntegrable_const + fun φ _ => ?_ + exact rpow_le_rpow_of_nonpos (by linarith) + (sub_le_sub_left (mul_le_of_le_one_right hm0 (sin_sq_le_one φ)) 1) (by norm_num) + rwa [intervalIntegral.integral_const, sub_zero, smul_eq_mul] at h + +/-! + +## D. Continuity on the domain + +The integrand is jointly continuous in `(m, φ)` on `(-∞, 1) × ℝ`, where the radicand is +positive, but not on all of `ℝ × ℝ`. Restricting the parameter to the subtype `Set.Iio 1` makes +the joint continuity global, so Mathlib's continuity of a parametric interval integral with +fixed endpoints (`intervalIntegral.continuous_parametric_intervalIntegral_of_continuous'`) +applies and gives continuity of `K` on the domain. Continuity at each point `m < 1` follows, +`(-∞, 1)` being a neighbourhood of `m`. + +-/ + +/-- `completeEllipticK` is continuous on its domain `(-∞, 1)`. -/ +lemma continuousOn_completeEllipticK : ContinuousOn completeEllipticK (Set.Iio 1) := by + rw [continuousOn_iff_continuous_domRestrict] + have hf : Continuous fun p : Set.Iio (1 : ℝ) × ℝ => + (1 - p.1.1 * sin p.2 ^ 2) ^ (-(1 / 2 : ℝ)) := by + refine Continuous.rpow_const ?_ fun p => Or.inl (completeEllipticK_radicand_pos p.1.2 p.2).ne' + fun_prop + exact intervalIntegral.continuous_parametric_intervalIntegral_of_continuous' + -- `f` must be named: higher-order unification cannot recover it from `Continuous f.uncurry`. + (f := fun (m : Set.Iio (1 : ℝ)) (φ : ℝ) => (1 - m.1 * sin φ ^ 2) ^ (-(1 / 2 : ℝ))) + hf 0 (π / 2) + +/-- `completeEllipticK` is continuous at every point `m < 1` of its domain, `(-∞, 1)` being a +neighbourhood of `m`. -/ +lemma continuousAt_completeEllipticK {m : ℝ} (hm : m < 1) : ContinuousAt completeEllipticK m := + continuousOn_completeEllipticK.continuousAt (Iio_mem_nhds hm) + +/-- `completeEllipticK` is continuous at `m = 0`, an interior point of its domain `(-∞, 1)`. -/ +lemma continuousAt_completeEllipticK_zero : ContinuousAt completeEllipticK 0 := + continuousAt_completeEllipticK zero_lt_one + +end Real diff --git a/Physlib/Mathematics/Trigonometry/SinSq.lean b/Physlib/Mathematics/Trigonometry/SinSq.lean new file mode 100644 index 0000000000..99f54a1d03 --- /dev/null +++ b/Physlib/Mathematics/Trigonometry/SinSq.lean @@ -0,0 +1,69 @@ +/- +Copyright (c) 2026 Aadarsh Agarwal. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Aadarsh Agarwal +-/ +module + +public import Mathlib.Analysis.SpecialFunctions.Trigonometric.Basic +/-! + +# Strict bounds on the square of the sine + +This file may eventually be upstreamed to Mathlib. + +## i. Overview + +Mathlib bounds the square of the sine by `sin x ^ 2 ≤ 1` for every real `x` (`Real.sin_sq_le_one`), +with equality exactly at the odd multiples of `π / 2`. This file records the strict form of that +bound, `sin x ^ 2 < 1` on the open interval `|x| < π / 2`, where the cosine is positive and +`1 - sin² x = cos² x`, together with its half-angle form `sin (θ / 2) ^ 2 < 1` for `|θ| < π`. + +Physlib uses the half-angle form for the simple pendulum: the parameter `sin² (θ₀ / 2)` of the +period formula lies in the domain `m < 1` of the complete elliptic integral `Real.completeEllipticK` +for every libration amplitude `|θ₀| < π`. + +## ii. Key results + +- `Real.sin_sq_lt_one` : `sin x ^ 2 < 1` for `|x| < π / 2`. +- `Real.sin_half_sq_lt_one` : `sin (θ / 2) ^ 2 < 1` for `|θ| < π`. + +## iii. Table of contents + +- A. Strict bounds on the square of the sine + +## iv. References + +- Landau & Lifshitz, Mechanics, 3rd ed., §11, Problem 1 (the pendulum period, whose parameter is + `sin² (θ₀ / 2)`). + +-/ + +@[expose] public section + +namespace Real + +/-! + +## A. Strict bounds on the square of the sine + +On `|x| < π / 2` the cosine is positive, so `1 - sin² x = cos² x` is positive; the half-angle form +follows by applying this at `x = θ / 2`. + +-/ + +/-- `sin x ^ 2 < 1` for `|x| < π / 2`: the cosine is positive there, and `1 - sin² x = cos² x`. -/ +lemma sin_sq_lt_one {x : ℝ} (h : |x| < π / 2) : sin x ^ 2 < 1 := by + obtain ⟨h₁, h₂⟩ := abs_lt.1 h + rw [← sub_pos, ← cos_sq'] + exact pow_pos (cos_pos_of_mem_Ioo ⟨by linarith, h₂⟩) 2 + +/-- The half-angle form of `sin_sq_lt_one`: `sin (θ / 2) ^ 2 < 1` for `|θ| < π`. For the pendulum +this says that the parameter `sin² (θ₀ / 2)` of the period formula lies in the domain of +`completeEllipticK` for every libration amplitude `|θ₀| < π`. -/ +lemma sin_half_sq_lt_one {θ : ℝ} (h : |θ| < π) : sin (θ / 2) ^ 2 < 1 := by + refine sin_sq_lt_one ?_ + rw [abs_div, abs_two] + linarith [abs_nonneg θ] + +end Real From f6d7fe3d32f1849a153147f2868d6db2d9daf40a Mon Sep 17 00:00:00 2001 From: aadarsh agarwal Date: Tue, 25 Aug 2026 11:58:42 -0500 Subject: [PATCH 13/20] feat(ClassicalMechanics): simple pendulum conservation, equilibria and shift invariance (#1569) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ClassicalMechanics): SimplePendulum variational equivalence and energy conservation - equationOfMotion_iff_gradLagrangian_zero: for smooth lifts the equation of motion holds iff the variational derivative of the action vanishes - isSolution_iff: solutions are exactly the smooth critical points of the action - energy_conservation_of_equationOfMotion: the time derivative of the energy vanishes along smooth lifts satisfying the equation of motion - energy_conservation_of_equationOfMotion': the energy at any time equals its initial value - IsSolution.energy_eq: energy conservation packaged for solutions Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 * feat(ClassicalMechanics): SimplePendulum equilibria, separatrix energy and energy bounds - equationOfMotion_const_zero, isSolution_const_zero: the hanging equilibrium, the first explicit solution of the pendulum - equationOfMotion_const_pi, isSolution_const_pi: the inverted equilibrium - equationOfMotion_const_iff: a constant lift solves the equation of motion iff the sine of its angle vanishes — the constant solutions are the equilibria - separatrixEnergy, separatrixEnergy_pos: the energy 2 m g ℓ of the inverted equilibrium, the threshold between libration and rotation - energy_const_pi: the energy of the inverted equilibrium is the separatrix energy - kineticEnergy_nonneg, inertia_mul_inner_deriv_le, potentialEnergy_le_energy: the elementary energy bounds along every lift of the angle - neg_one_lt_cos_of_energy_lt: libration — below the separatrix energy the bob never reaches the top of the swing - deriv_ne_zero_of_energy_gt: rotation — above the separatrix energy the angular velocity never vanishes - potentialEnergy_eq_energy_of_deriv_eq_zero: turning points — where the velocity vanishes the potential energy equals the total energy Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 * feat(ClassicalMechanics): SimplePendulum dynamics 2πn-shift invariance Section K (Independence of the lift): the lifted dynamics of the simple pendulum is invariant under shifting the lift by a whole number of turns, so it descends to the configuration space. New declarations: - SimplePendulum.potentialEnergy_add_two_pi - SimplePendulum.torque_add_two_pi - SimplePendulum.energy_add_const - SimplePendulum.equationOfMotion_add_two_pi - SimplePendulum.isSolution_add_two_pi - SimplePendulum.ofAngle_add_two_pi_coord Module doc: the Overview now points at section K for the 2πn statement, and the Key results name the equilibria as solutions (isSolution_const_zero, isSolution_const_pi) and list the section K results. Imports gain the geometric configuration space module for ConfigurationSpace.ofAngle. API map: two new rows (equilibria as solutions with the libration/rotation threshold; invariance of the lifted dynamics under whole-turn shifts) and the Overview extended to match. Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 * fix(ClassicalMechanics): review fixes for the simple pendulum conservation module Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 * refactor(ClassicalMechanics): split the simple pendulum module into Basic, Equilibria and LiftInvariance - `SimplePendulum/Basic.lean` keeps the model and its dynamics (sections A–H): the input data, energies, Lagrangian, torque, equation of motion, variational principle and energy conservation. - New `SimplePendulum/Equilibria.lean` (old sections I–J): the hanging and inverted equilibria, the constant solutions as the equilibria, the separatrix energy, the energy bounds, libration/rotation and turning points. - New `SimplePendulum/LiftInvariance.lean` (old section K): invariance of the energies, the torque, the equation of motion and its solutions under `θ ↦ θ + 2πn`, and the shifted lift describing the same configuration. Takes over the `Geometric.Basic` import, which nothing in `Basic.lean` used. - Declarations and proofs are moved verbatim; only section labels, cross-references and the module docs change. `Physlib.lean` and the API map updated. Requested by review on #1569. Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 --------- Co-authored-by: Claude Fable 5 Co-authored-by: Codex GPT-5.6 --- Physlib.lean | 2 + .../Pendulum/SimplePendulum/API-map.yaml | 22 +- .../Pendulum/SimplePendulum/Basic.lean | 155 +++++++- .../Pendulum/SimplePendulum/Equilibria.lean | 336 ++++++++++++++++++ .../SimplePendulum/LiftInvariance.lean | 203 +++++++++++ 5 files changed, 700 insertions(+), 18 deletions(-) create mode 100644 Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Equilibria.lean create mode 100644 Physlib/ClassicalMechanics/Pendulum/SimplePendulum/LiftInvariance.lean diff --git a/Physlib.lean b/Physlib.lean index 6ee5884102..db65baeb26 100644 --- a/Physlib.lean +++ b/Physlib.lean @@ -18,7 +18,9 @@ public import Physlib.ClassicalMechanics.OrbitalMechanics.VisViva public import Physlib.ClassicalMechanics.Pendulum.CoplanarDoublePendulum public import Physlib.ClassicalMechanics.Pendulum.MiscellaneousPendulumPivotMotions public import Physlib.ClassicalMechanics.Pendulum.SimplePendulum.Basic +public import Physlib.ClassicalMechanics.Pendulum.SimplePendulum.Equilibria public import Physlib.ClassicalMechanics.Pendulum.SimplePendulum.Geometric.Basic +public import Physlib.ClassicalMechanics.Pendulum.SimplePendulum.LiftInvariance public import Physlib.ClassicalMechanics.Pendulum.SlidingPendulum public import Physlib.ClassicalMechanics.RigidBody.AngularMomentum public import Physlib.ClassicalMechanics.RigidBody.AngularVelocity diff --git a/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml index c6e03bb1dd..174249c0ff 100644 --- a/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml +++ b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/API-map.yaml @@ -10,8 +10,12 @@ Overview: | small amplitudes it is harmonic with period 2π√(ℓ/g); for librations (amplitudes below the inverted position) the period grows with the amplitude and is given by a complete elliptic integral. This API records the configuration space with its manifold structure and its - embedding into physical space, together with the lifted Lagrangian and equation of motion; - the small-angle limit and the period follow in later modules. + embedding into physical space, together with the lifted Lagrangian and equation of motion, + the equivalence of that equation with the vanishing of the variational gradient of the + action, the conservation of energy, the equilibria with the separatrix energy and the + below/above-threshold energy bounds, and the invariance of the lifted dynamics under + shifting the angle by whole turns; the small-angle limit and the period follow in later + modules. ParentAPIs: - "Space (Physlib/SpaceAndTime/Space)" @@ -48,6 +52,14 @@ Requirements: done: true location: Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Basic.lean (SimplePendulum.lagrangian, SimplePendulum.torque, SimplePendulum.EquationOfMotion, SimplePendulum.equationOfMotion_iff_scalar) - - description: The API shall contain the equivalence of the equation of motion with the vanishing of the variational gradient of the action, and energy conservation. - done: false - location: N/A + - description: The API contains the equivalence of the equation of motion with the vanishing of the variational gradient of the action, and energy conservation. + done: true + location: Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Basic.lean (SimplePendulum.equationOfMotion_iff_gradLagrangian_zero, SimplePendulum.energy_conservation_of_equationOfMotion) + + - description: The API contains the equilibria of the pendulum as solutions, together with the separatrix energy and the below/above-threshold energy bounds. + done: true + location: Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Equilibria.lean (SimplePendulum.equationOfMotion_const_zero, SimplePendulum.equationOfMotion_const_pi, SimplePendulum.isSolution_const_zero, SimplePendulum.isSolution_const_pi, SimplePendulum.separatrixEnergy, SimplePendulum.neg_one_lt_cos_of_energy_lt, SimplePendulum.deriv_ne_zero_of_energy_gt) + + - description: The API contains the invariance of the lifted dynamics under shifting the angle by whole turns. + done: true + location: Physlib/ClassicalMechanics/Pendulum/SimplePendulum/LiftInvariance.lean (SimplePendulum.equationOfMotion_add_int_mul_two_pi, SimplePendulum.energy_add_int_mul_two_pi, SimplePendulum.isSolution_add_int_mul_two_pi) diff --git a/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Basic.lean b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Basic.lean index d6915a22e9..f39faa256c 100644 --- a/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Basic.lean +++ b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Basic.lean @@ -31,9 +31,11 @@ file are written instead on the Euclidean lift `Time → EuclideanSpace ℝ (Fin carried by a real number, from which the configuration is recovered by `SimplePendulum.ConfigurationSpace.ofAngle`, and the one-dimensional Euclidean space stands in for both the configuration space and its tangent space, so that the Euler–Lagrange operator of Physlib -applies verbatim. Two lifts differing by `2π n` describe the same motion, as a subsequent -contribution proves; the connection of the model here with the geometric configuration space is -made in a later module. +applies verbatim. Two lifts differing by `2π n` describe the same motion: the invariance of the +dynamics under such shifts is proved in the module `SimplePendulum.LiftInvariance`, and the full +connection of the model here with the geometric configuration space is made in a later module. +The first consequences of the dynamics — the equilibria, and the below- and above-separatrix +energy bounds characteristic of libration and rotation — follow in `SimplePendulum.Equilibria`. ## ii. Key results @@ -55,7 +57,14 @@ made in a later module. `SimplePendulum.IsSolution` is a smooth solution of it. - `SimplePendulum.gradLagrangian` is the variational derivative of the action, computed by `gradLagrangian_eq_eulerLagrangeOp` and `gradLagrangian_eq_torque`. - +- `SimplePendulum.equationOfMotion_iff_gradLagrangian_zero` identifies the equation of motion, + for smooth lifts of the angle, with the vanishing of the variational derivative of the action, + and `SimplePendulum.isSolution_iff` characterizes the solutions as the smooth critical points + of the action. +- `SimplePendulum.energy_conservation_of_equationOfMotion`, + `SimplePendulum.energy_conservation_of_equationOfMotion'` and + `SimplePendulum.IsSolution.energy_eq` express the conservation of energy along the motions of + the pendulum. ## iii. Table of contents - A. The input data @@ -82,7 +91,13 @@ made in a later module. - F.1. The definition of the variational derivative - F.2. Equality with the Euler–Lagrange operator - F.3. The variational derivative in terms of the torque - +- G. Equation of motion and the variational principle + - G.1. Equivalence with the vanishing of the variational derivative + - G.2. The variational characterization of solutions +- H. Energy conservation + - H.1. Energy conservation in terms of time derivatives + - H.2. Energy conservation in terms of constant energy + - H.3. Energy conservation for solutions ## iv. References References for the simple gravity pendulum include: @@ -547,9 +562,8 @@ time. A rough lift can still satisfy the equation accidentally — a discontinuo between equilibrium angles solves it, as section E.3 explains — which is why the notion of a solution, `IsSolution`, demands smoothness as well. It is also the form in which the equation of motion is solved and used. The two agree for smooth lifts, by -`equationOfMotion_iff_gradLagrangian_zero`, proved in a subsequent contribution; the present -module goes as far as `gradLagrangian_eq_torque`, from which that equivalence is one rearrangement -away. +`equationOfMotion_iff_gradLagrangian_zero` of section G, which is one rearrangement away from +`gradLagrangian_eq_torque` of section F. -/ @@ -595,8 +609,7 @@ about the pivot with the torque of gravity, at every instant. This pointwise relation, and not the vanishing of the variational derivative of the action, is the definition of the equation of motion here; see the discussion in section E. For a smooth - lift of the angle the two agree, by `equationOfMotion_iff_gradLagrangian_zero` in a subsequent - contribution. -/ + lift of the angle the two agree, by `equationOfMotion_iff_gradLagrangian_zero` of section G. -/ def EquationOfMotion (θ : Time → EuclideanSpace ℝ (Fin 1)) : Prop := ∀ t, S.inertia • ∂ₜ (∂ₜ θ) t = S.torque (θ t) @@ -732,9 +745,8 @@ lemma gradLagrangian_eq_eulerLagrangeOp (θ : Time → EuclideanSpace ℝ (Fin 1 Evaluating the Euler–Lagrange operator with the gradients of section D.3 gives the variational derivative as the torque minus the rate of change of the angular momentum. Its vanishing is -therefore the equation of motion of section E; that equivalence, -`equationOfMotion_iff_gradLagrangian_zero`, is proved in a subsequent contribution, together -with energy conservation, so that this module carries the model of the pendulum alone. +therefore the equation of motion of section E; that equivalence is +`equationOfMotion_iff_gradLagrangian_zero` of section G. -/ @@ -747,6 +759,123 @@ lemma gradLagrangian_eq_torque (θ : Time → EuclideanSpace ℝ (Fin 1)) (hθ : simp [S.gradient_lagrangian_position_eq, S.gradient_lagrangian_velocity_eq, S.torque_eq, Time.deriv_smul _ S.inertia (deriv_differentiable_of_contDiff θ hθ)] +/-! + +## G. Equation of motion and the variational principle + +Section E took the pointwise balance of the angular momentum's rate of change against the torque +as the definition of the equation of motion, and section F computed the variational derivative of +the action. This section proves that for smooth lifts of the angle the two agree: the pointwise +law is exactly the Euler–Lagrange equation of the action, the statement that the motion is a +critical point of the action. The equivalence holds only under smoothness — the variational +derivative is `0` by convention on lifts too rough to admit a variational gradient, so on such +lifts its vanishing says nothing — which is why section E took the pointwise form as primary. + +-/ + +/-! + +### G.1. Equivalence with the vanishing of the variational derivative + +By `gradLagrangian_eq_torque` the variational derivative of the action along a smooth lift is +the torque minus the rate of change of the angular momentum, so its vanishing is a rearrangement +of the equation of motion. + +-/ + +/-- For a smooth lift of the angle the equation of motion of the simple pendulum holds if and + only if the variational derivative of the action vanishes: the smooth motions of the pendulum + are the critical points of its action. -/ +lemma equationOfMotion_iff_gradLagrangian_zero (θ : Time → EuclideanSpace ℝ (Fin 1)) + (hθ : ContDiff ℝ ∞ θ) : + S.EquationOfMotion θ ↔ S.gradLagrangian θ = 0 := by + rw [S.gradLagrangian_eq_torque θ hθ, funext_iff] + simp only [EquationOfMotion, Pi.zero_apply, sub_eq_zero] + exact forall_congr' fun t => eq_comm + +/-! + +### G.2. The variational characterization of solutions + +A solution was defined in section E.3 as a smooth lift satisfying the equation of motion. +Substituting the equivalence of G.1 for the equation of motion turns this into the variational +characterization: the solutions of the pendulum are exactly the smooth lifts of the angle along +which the variational derivative of the action vanishes. + +-/ + +/-- A lift of the angle is a solution of the simple pendulum if and only if it is smooth and the + variational derivative of the action vanishes along it. -/ +lemma isSolution_iff (θ : Time → EuclideanSpace ℝ (Fin 1)) : + S.IsSolution θ ↔ ContDiff ℝ ∞ θ ∧ S.gradLagrangian θ = 0 := + and_congr_right fun hθ => S.equationOfMotion_iff_gradLagrangian_zero θ hθ + +/-! + +## H. Energy conservation + +The pendulum is conservative: along any smooth lift of the angle satisfying the equation of +motion the energy is constant. No computation remains to be done here: by `energy_deriv` the +rate of change of the energy is the angular velocity paired with the sum of `I θ̈` and the +gradient of the potential, and the equation of motion is exactly the vanishing of that sum. + +-/ + +/-! + +### H.1. Energy conservation in terms of time derivatives + +The first form of energy conservation: the time derivative of the energy vanishes identically +along any smooth lift of the angle satisfying the equation of motion. + +-/ + +/-- Along a smooth lift of the angle satisfying the equation of motion the time derivative of + the energy of the simple pendulum vanishes. -/ +lemma energy_conservation_of_equationOfMotion (θ : Time → EuclideanSpace ℝ (Fin 1)) + (hθ : ContDiff ℝ ∞ θ) (h : S.EquationOfMotion θ) : ∂ₜ (S.energy θ) = 0 := by + rw [S.equationOfMotion_iff_newtons_2nd_law θ] at h + funext t + rw [S.energy_deriv θ hθ] + simp [h t] + +/-! + +### H.2. Energy conservation in terms of constant energy + +The second form: the energy is differentiable in time along a smooth lift of the angle, so the +vanishing of its derivative makes it a constant function of the time, equal to its initial +value. + +-/ + +/-- Along a smooth lift of the angle satisfying the equation of motion the energy of the simple + pendulum at any time is equal to its initial value. -/ +lemma energy_conservation_of_equationOfMotion' (θ : Time → EuclideanSpace ℝ (Fin 1)) + (hθ : ContDiff ℝ ∞ θ) (h : S.EquationOfMotion θ) (t : Time) : + S.energy θ t = S.energy θ 0 := by + apply is_const_of_fderiv_eq_zero (𝕜 := ℝ) (S.energy_differentiable θ hθ) + intro t + ext p + rw [p.eq_one_smul, map_smul, ← Time.deriv_eq, + S.energy_conservation_of_equationOfMotion θ hθ h] + simp + +/-! + +### H.3. Energy conservation for solutions + +The hypotheses of energy conservation — smoothness and the equation of motion — are exactly the +two components of being a solution, so for solutions conservation takes its most compact form. + +-/ + +/-- The energy of the simple pendulum along a solution at any time is equal to its initial + value. -/ +lemma IsSolution.energy_eq {S : SimplePendulum} {θ : Time → EuclideanSpace ℝ (Fin 1)} + (h : S.IsSolution θ) (t : Time) : S.energy θ t = S.energy θ 0 := + S.energy_conservation_of_equationOfMotion' θ h.contDiff h.equationOfMotion t + end SimplePendulum end ClassicalMechanics diff --git a/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Equilibria.lean b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Equilibria.lean new file mode 100644 index 0000000000..a665994e2a --- /dev/null +++ b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/Equilibria.lean @@ -0,0 +1,336 @@ +/- +Copyright (c) 2026 Aadarsh Agarwal. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Aadarsh Agarwal +-/ +module + +public import Physlib.ClassicalMechanics.Pendulum.SimplePendulum.Basic +/-! + +# Equilibria and energy regimes of the simple pendulum + +## i. Overview + +The equation of motion of the simple gravity pendulum, `I θ̈ = -m g ℓ sin θ` in the lifted +formulation of `SimplePendulum.Basic`, has two elementary consequences that follow from the +vanishing of the torque and from energy conservation alone, before any non-constant solution is +constructed. The first is the equilibria: the torque of gravity vanishes exactly where `sin θ` +does, so the constant lifts at the angle `0` — the bob hanging at rest below the pivot — and at +the angle `π` — the bob balanced above it — solve the equation of motion, and conversely a +constant lift is a solution only at the multiples of `π`. The second is the division of the +smooth motions into regimes by the value of the conserved energy. The threshold is the energy +`2 m g ℓ` of the inverted equilibrium, the separatrix energy: below it the potential energy +cannot reach its value at the top of the swing, so the bob never gets there — classically the +regime of libration, the bob swinging back and forth; above it the kinetic energy never +vanishes, so the bob never halts — classically the regime of rotation, the pendulum circulating +over the top. This is the phase portrait of the pendulum drawn in Arnold §4, whose level curves +of the energy are closed ovals below the threshold and unbounded waves above it. + +As in `SimplePendulum.Basic`, the motion is written on the Euclidean lift +`Time → EuclideanSpace ℝ (Fin 1)` of the angle. Only the bounds characteristic of each regime +are proved here: the librating and rotating motions themselves, and the instability of the +inverted equilibrium, are statements about non-constant solutions and are not constructed in +this module. + +## ii. Key results + +- `SimplePendulum.equationOfMotion_const_zero` and `SimplePendulum.equationOfMotion_const_pi` + are the hanging and the inverted equilibrium, packaged as the simplest explicit solutions of + the pendulum by `SimplePendulum.isSolution_const_zero` and `SimplePendulum.isSolution_const_pi`, + and `SimplePendulum.equationOfMotion_const_iff` shows that the constant solutions are exactly + the equilibria. +- `SimplePendulum.separatrixEnergy` is the energy `2 m g ℓ` of the inverted equilibrium + (`SimplePendulum.energy_const_pi`), the threshold between libration and rotation. +- `SimplePendulum.neg_one_lt_cos_of_energy_lt`: below the threshold the bob never reaches the + top of the swing. `SimplePendulum.deriv_ne_zero_of_energy_gt`: above it the angular velocity + never vanishes. `SimplePendulum.potentialEnergy_eq_energy_of_deriv_eq_zero`: at a turning + point the potential energy equals the total energy. + +## iii. Table of contents + +- A. Equilibria + - A.1. The hanging equilibrium + - A.2. The inverted equilibrium + - A.3. The constant solutions are the equilibria +- B. Energy regimes + - B.1. The separatrix energy + - B.2. Energy bounds + - B.3. Libration and rotation + - B.4. Turning points + +## iv. References + +References for the equilibria and the energy regimes of the simple pendulum include: +- Landau & Lifshitz, Mechanics, 3rd ed., §11 (motion in one dimension: the turning points, and + finite and infinite motion according to the energy). +- Arnold, Mathematical Methods of Classical Mechanics, 2nd ed., §4 (the phase portrait of the + pendulum). + +-/ + +@[expose] public section + +namespace ClassicalMechanics +open Real InnerProductSpace Time +open scoped ContDiff + +namespace SimplePendulum + +variable (S : SimplePendulum) + +/-! + +## A. Equilibria + +The two configurations at which the torque of gravity vanishes — the bob hanging at rest below +the pivot and the bob balanced above it — give constant solutions of the equation of motion, the +simplest explicit solutions of the pendulum. This section verifies the two, and proves the +converse: a constant lift solves the equation of motion only where the torque vanishes, that is +only at the angles `π n`. The constant solutions are exactly the equilibria. + +-/ + +/-! + +### A.1. The hanging equilibrium + +At the angle `0` the bob hangs at rest at the bottom of its swing. The lift is constant, so the +angular momentum does not change, and the torque vanishes with `sin 0`: both sides of the +equation of motion are zero. + +-/ + +/-- The constant lift at the angle `0` — the bob hanging at rest at the bottom of its swing — + satisfies the equation of motion of the simple pendulum. -/ +lemma equationOfMotion_const_zero : + S.EquationOfMotion (fun _ => (0 : EuclideanSpace ℝ (Fin 1))) := by + intro t + have h1 : ∂ₜ (fun _ : Time => (0 : EuclideanSpace ℝ (Fin 1))) = fun _ => 0 := by + funext s + simp + rw [h1] + simp [torque_eq] + +/-- The hanging equilibrium is a solution of the simple pendulum: the constant lift at the angle + `0` is smooth and satisfies the equation of motion. It is the simplest explicit solution of the + pendulum. -/ +lemma isSolution_const_zero : S.IsSolution (fun _ => 0) := + ⟨contDiff_const, S.equationOfMotion_const_zero⟩ + +/-! + +### A.2. The inverted equilibrium + +At the angle `π` the bob is balanced directly above the pivot, where the torque vanishes with +`sin π`; the pendulum stays there. That this balance is unstable — neighbouring solutions run +away from it — is a statement about non-constant solutions, and is not proved here. + +-/ + +/-- The constant lift at the angle `π` — the bob balanced directly above the pivot — satisfies + the equation of motion of the simple pendulum. -/ +lemma equationOfMotion_const_pi : + S.EquationOfMotion (fun _ => EuclideanSpace.single 0 Real.pi) := by + intro t + have h1 : ∂ₜ (fun _ : Time => EuclideanSpace.single (0 : Fin 1) Real.pi) = fun _ => 0 := by + funext s + simp + rw [h1] + simp [torque_eq] + +/-- The inverted equilibrium is a solution of the simple pendulum: the constant lift at the + angle `π` is smooth and satisfies the equation of motion. -/ +lemma isSolution_const_pi : S.IsSolution (fun _ => EuclideanSpace.single 0 Real.pi) := + ⟨contDiff_const, S.equationOfMotion_const_pi⟩ + +/-! + +### A.3. The constant solutions are the equilibria + +For a constant lift the angular momentum does not change, so the equation of motion reduces to +the vanishing of the torque, that is to `sin θ = 0`, which holds exactly at the multiples of +`π`. The constant solutions are therefore exactly the equilibria: the hanging equilibrium, the +inverted equilibrium, and their copies shifted by whole turns. + +-/ + +/-- A constant lift satisfies the equation of motion of the simple pendulum if and only if the + sine of its angle vanishes — classically, the angles straight down and straight up: the + constant solutions are exactly the equilibria. -/ +lemma equationOfMotion_const_iff (x : EuclideanSpace ℝ (Fin 1)) : + S.EquationOfMotion (fun _ => x) ↔ Real.sin (x 0) = 0 := by + have h1 : ∂ₜ (fun _ : Time => x) = fun _ => 0 := by + funext s + simp + have he : EuclideanSpace.single (0 : Fin 1) (1 : ℝ) ≠ 0 := + fun h => one_ne_zero ((PiLp.single_eq_zero_iff 2 (0 : Fin 1)).mp h) + have hc : S.m * S.g * S.ℓ ≠ 0 := (mul_pos (mul_pos S.m_pos S.g_pos) S.ℓ_pos).ne' + simp only [EquationOfMotion, h1, Time.deriv_const, smul_zero, forall_const] + rw [eq_comm, torque_eq, neg_eq_zero, smul_eq_zero, or_iff_left he, mul_eq_zero, + or_iff_right hc] + +/-! + +## B. Energy regimes + +Energy conservation divides the smooth motions of the pendulum into regimes according to the +value of the conserved energy, the threshold being the energy `2 m g ℓ` of the inverted +equilibrium. Below the threshold the potential energy cannot reach its value at the top of the +swing, so the bob never reaches the top; classically this is the regime of libration, the bob +swinging back and forth. Above the threshold the kinetic energy can never vanish, so the bob +never halts; classically this is the regime of rotation, the pendulum circulating over the top. +This is the phase portrait of the pendulum drawn in Arnold §4, whose level curves of the energy +are closed ovals below the threshold and unbounded waves above it. This section proves the +below- and above-threshold bounds characteristic of each regime, from two elementary bounds +relating the energies — the librating and rotating motions themselves are not constructed +here — and characterizes the turning points, the instants at which the velocity vanishes and +the potential energy exhausts the total energy. + +-/ + +/-! + +### B.1. The separatrix energy + +The threshold between the regimes is the energy of the inverted equilibrium: no kinetic energy, +and the potential energy `2 m g ℓ` of the top of the swing. It is called the separatrix energy +after the curve it names in the phase portrait, the level set of the energy separating the +closed orbits of libration from the unbounded orbits of rotation. Only the threshold value is +used in this file: the separatrix motions themselves — the non-constant solutions asymptotic to +the inverted equilibrium — are not constructed here. + +-/ + +/-- The separatrix energy of the simple pendulum is `2 m g ℓ`, the energy of the inverted + equilibrium. It is the threshold separating the two regimes of the motion, libration below it + and rotation above it. -/ +def separatrixEnergy : ℝ := 2 * (S.m * S.g * S.ℓ) + +/-- The separatrix energy of the simple pendulum, written out. -/ +lemma separatrixEnergy_eq : S.separatrixEnergy = 2 * (S.m * S.g * S.ℓ) := rfl + +/-- The separatrix energy of the simple pendulum is positive. -/ +lemma separatrixEnergy_pos : 0 < S.separatrixEnergy := + mul_pos two_pos (mul_pos (mul_pos S.m_pos S.g_pos) S.ℓ_pos) + +/-- The energy of the simple pendulum along the inverted equilibrium is the separatrix energy: + the bob balanced at the top has no kinetic energy and the full potential energy `2 m g ℓ`. -/ +lemma energy_const_pi : + S.energy (fun _ => EuclideanSpace.single 0 Real.pi) = fun _ => S.separatrixEnergy := by + funext t + simp only [energy_eq, kineticEnergy_eq, Time.deriv_const, inner_zero_left, mul_zero, + zero_add, potentialEnergy_eq, separatrixEnergy_eq, PiLp.single_apply, reduceIte, Real.cos_pi] + ring + +/-! + +### B.2. Energy bounds + +Two elementary bounds drive the regime theorems: the kinetic energy is non-negative, so the +potential energy is at most the total energy; and the potential energy is non-negative, so +`I θ̇²` is at most twice the total energy. None of the bounds of this subsection uses the +equation of motion — they hold along every lift of the angle. + +-/ + +/-- The kinetic energy of the simple pendulum is non-negative along every lift of the angle. -/ +lemma kineticEnergy_nonneg (θ : Time → EuclideanSpace ℝ (Fin 1)) (t : Time) : + 0 ≤ S.kineticEnergy θ t := by + simp only [kineticEnergy_eq] + exact mul_nonneg (mul_nonneg (by norm_num) S.inertia_pos.le) real_inner_self_nonneg + +/-- The moment of inertia times the square of the angular speed, `I θ̇²`, is at most twice the + total energy, along every lift of the angle. -/ +lemma inertia_mul_inner_deriv_le (θ : Time → EuclideanSpace ℝ (Fin 1)) (t : Time) : + S.inertia * ⟪∂ₜ θ t, ∂ₜ θ t⟫_ℝ ≤ 2 * S.energy θ t := by + have hV := S.potentialEnergy_nonneg (θ t) + have hE : S.energy θ t = (1 / (2 : ℝ)) * S.inertia * ⟪∂ₜ θ t, ∂ₜ θ t⟫_ℝ + + S.potentialEnergy (θ t) := by + rw [energy_eq, kineticEnergy_eq] + linarith + +/-- The potential energy of the simple pendulum is at most the total energy along every lift of + the angle. -/ +lemma potentialEnergy_le_energy (θ : Time → EuclideanSpace ℝ (Fin 1)) (t : Time) : + S.potentialEnergy (θ t) ≤ S.energy θ t := by + have hK := S.kineticEnergy_nonneg θ t + have hE : S.energy θ t = S.kineticEnergy θ t + S.potentialEnergy (θ t) := by + rw [energy_eq] + linarith + +/-! + +### B.3. Libration and rotation + +Along a smooth solution with energy below the separatrix energy, the potential energy — being +at most the conserved total energy — stays strictly below `2 m g ℓ`, so the cosine of the angle +stays strictly above `-1`: the bob never reaches the top of the swing, and the motion is a +libration, swinging back and forth — though only the bound is proved here. Along a smooth +solution with energy above the separatrix energy the angular velocity can never vanish, for at +such an instant the whole energy would be potential, and the potential energy never exceeds +`2 m g ℓ`; the velocity being continuous, it keeps a fixed sign, and the motion is a rotation +over the top — though only the non-vanishing is proved here. + +-/ + +/-- Libration: along a smooth lift of the angle satisfying the equation of motion, with energy + below the separatrix energy, the cosine of the angle stays strictly above `-1` — the bob + never reaches the top of the swing. -/ +lemma neg_one_lt_cos_of_energy_lt (θ : Time → EuclideanSpace ℝ (Fin 1)) + (hθ : ContDiff ℝ ∞ θ) (h : S.EquationOfMotion θ) + (hE : S.energy θ 0 < S.separatrixEnergy) (t : Time) : -1 < Real.cos (θ t 0) := by + have hc : 0 < S.m * S.g * S.ℓ := mul_pos (mul_pos S.m_pos S.g_pos) S.ℓ_pos + have hV : S.m * S.g * S.ℓ * (1 - Real.cos (θ t 0)) < 2 * (S.m * S.g * S.ℓ) := by + rw [← S.potentialEnergy_eq (θ t), ← S.separatrixEnergy_eq] + calc S.potentialEnergy (θ t) ≤ S.energy θ t := S.potentialEnergy_le_energy θ t + _ = S.energy θ 0 := S.energy_conservation_of_equationOfMotion' θ hθ h t + _ < S.separatrixEnergy := hE + nlinarith [hV, hc] + +/-- Rotation: along a smooth lift of the angle satisfying the equation of motion, with energy + above the separatrix energy, the angular velocity never vanishes — the bob never halts. -/ +lemma deriv_ne_zero_of_energy_gt (θ : Time → EuclideanSpace ℝ (Fin 1)) + (hθ : ContDiff ℝ ∞ θ) (h : S.EquationOfMotion θ) + (hE : S.separatrixEnergy < S.energy θ 0) (t : Time) : ∂ₜ θ t ≠ 0 := by + intro h0 + have hK : S.kineticEnergy θ t = 0 := by + simp only [kineticEnergy_eq] + simp [h0] + have ht : S.energy θ t = S.kineticEnergy θ t + S.potentialEnergy (θ t) := by + rw [energy_eq] + have hle := S.potentialEnergy_le (θ t) + have hcons := S.energy_conservation_of_equationOfMotion' θ hθ h t + have hsep : S.separatrixEnergy = 2 * (S.m * S.g * S.ℓ) := S.separatrixEnergy_eq + linarith + +/-! + +### B.4. Turning points + +At an instant where the angular velocity vanishes the kinetic energy vanishes with it, and the +conserved total energy is purely potential. These are the turning points of the motion, where a +librating bob halts at the extremes of its arc before swinging back; by the rotation theorem of +B.3 they can occur only at energies not above the separatrix energy. + +-/ + +/-- Turning points: along a smooth lift of the angle satisfying the equation of motion, at an + instant where the angular velocity vanishes, the potential energy equals the conserved total + energy. -/ +lemma potentialEnergy_eq_energy_of_deriv_eq_zero (θ : Time → EuclideanSpace ℝ (Fin 1)) + (hθ : ContDiff ℝ ∞ θ) (h : S.EquationOfMotion θ) (t : Time) (h0 : ∂ₜ θ t = 0) : + S.potentialEnergy (θ t) = S.energy θ 0 := by + have hK : S.kineticEnergy θ t = 0 := by + simp only [kineticEnergy_eq] + simp [h0] + have ht : S.energy θ t = S.kineticEnergy θ t + S.potentialEnergy (θ t) := by + rw [energy_eq] + have hcons := S.energy_conservation_of_equationOfMotion' θ hθ h t + linarith + +end SimplePendulum + +end ClassicalMechanics + +end diff --git a/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/LiftInvariance.lean b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/LiftInvariance.lean new file mode 100644 index 0000000000..a46d04bf4f --- /dev/null +++ b/Physlib/ClassicalMechanics/Pendulum/SimplePendulum/LiftInvariance.lean @@ -0,0 +1,203 @@ +/- +Copyright (c) 2026 Aadarsh Agarwal. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Aadarsh Agarwal +-/ +module + +public import Physlib.ClassicalMechanics.Pendulum.SimplePendulum.Basic +public import Physlib.ClassicalMechanics.Pendulum.SimplePendulum.Geometric.Basic +/-! + +# Independence of the lift for the simple pendulum + +## i. Overview + +The dynamics of the simple gravity pendulum in `SimplePendulum.Basic` is written on a lift of the +motion: the real angle `θ t 0` stands for the configuration `ConfigurationSpace.ofAngle (θ t 0)`, +and two lifts differing by a whole number of turns carry the same configurations. For the lifted +formulation to describe the pendulum faithfully, nothing dynamical may depend on the choice of +the lift. This module proves that the dynamical quantities — the potential and kinetic energies, +the torque, the energy, and the equation of motion together with its solutions — are invariant +under the deck transformations `θ ↦ θ + 2π n` of the angular lift, and closes by making the +starting point precise: the shifted lift describes the same configuration, by the periodicity of +the angular lift of the geometric configuration space. The packaging of this invariance at the +level of configuration-space trajectories comes with the geometric bridge in a later module. + +## ii. Key results + +- `SimplePendulum.potentialEnergy_add_int_mul_two_pi` and + `SimplePendulum.torque_add_int_mul_two_pi`: the potential energy and the torque are unchanged + by shifting the angle by a whole number of turns. +- `SimplePendulum.kineticEnergy_add_const` and `SimplePendulum.energy_add_int_mul_two_pi`: the + kinetic energy is unchanged by any constant shift of the lift, and the energy by a shift by a + whole number of turns. +- `SimplePendulum.equationOfMotion_add_int_mul_two_pi` and + `SimplePendulum.isSolution_add_int_mul_two_pi`: the equation of motion and its solutions are + invariant under shifting the lift by a whole number of turns. +- `SimplePendulum.ofAngle_add_int_mul_two_pi_coord`: the shifted lift describes the same + configuration. + +## iii. Table of contents + +- A. Independence of the lift + - A.1. Invariance of the potential energy and the torque + - A.2. Invariance of the energy + - A.3. Invariance of the equation of motion and its solutions + - A.4. The shifted lift describes the same configuration + +## iv. References + +References for the simple gravity pendulum include: +- Landau & Lifshitz, Mechanics, 3rd ed., §5 and §21. +- Arnold, Mathematical Methods of Classical Mechanics, 2nd ed., §4. + +-/ + +@[expose] public section + +namespace ClassicalMechanics +open Real InnerProductSpace Time +open scoped ContDiff + +namespace SimplePendulum + +variable (S : SimplePendulum) + +/-! + +## A. Independence of the lift + +The dynamics of `SimplePendulum.Basic` are written on a lift of the motion: the real angle +`θ t 0` stands for the configuration `ConfigurationSpace.ofAngle (θ t 0)`, and two lifts +differing by a whole number of turns carry the same configurations. This section proves that the +dynamical quantities listed below — the energies, the torque, and the equation of motion and its +solutions — are invariant under the deck transformations `θ ↦ θ + 2π n` of the angular lift; +the packaging of this invariance at the level of configuration-space trajectories comes with +the geometric bridge in a later module. The section closes by making the starting point +precise: the shifted lift does describe the same configuration, by the periodicity of the +angular lift of the geometric configuration space. + +-/ + +/-! + +### A.1. Invariance of the potential energy and the torque + +The potential energy and the torque depend on the angle only through its cosine and its sine, +and both have period `2π`: neither quantity changes when the angle is shifted by a whole number +of turns. + +-/ + +/-- The potential energy of the simple pendulum is invariant under shifting the angle by a + whole number of turns. -/ +lemma potentialEnergy_add_int_mul_two_pi (x : EuclideanSpace ℝ (Fin 1)) (n : ℤ) : + S.potentialEnergy (x + (n * (2 * Real.pi)) • EuclideanSpace.single 0 1) = + S.potentialEnergy x := by + have h0 : (x + (n * (2 * Real.pi)) • EuclideanSpace.single 0 1 : EuclideanSpace ℝ (Fin 1)) 0 = + x 0 + n * (2 * Real.pi) := by + simp + rw [potentialEnergy_eq, potentialEnergy_eq, h0, Real.cos_add_int_mul_two_pi] + +/-- The torque of the simple pendulum is invariant under shifting the angle by a whole number + of turns. -/ +lemma torque_add_int_mul_two_pi (x : EuclideanSpace ℝ (Fin 1)) (n : ℤ) : + S.torque (x + (n * (2 * Real.pi)) • EuclideanSpace.single 0 1) = S.torque x := by + have h0 : (x + (n * (2 * Real.pi)) • EuclideanSpace.single 0 1 : EuclideanSpace ℝ (Fin 1)) 0 = + x 0 + n * (2 * Real.pi) := by + simp + rw [torque_eq, torque_eq, h0, Real.sin_add_int_mul_two_pi] + +/-! + +### A.2. Invariance of the energy + +The shift of the lift is constant in time, so it drops out of the velocity, and the kinetic +energy is unchanged by any constant shift at all; the potential energy is unchanged by the +invariance of A.1. Together the two give the invariance of the energy under shifting the lift +by a whole number of turns. + +-/ + +/-- The kinetic energy of the simple pendulum along a lift of the angle is invariant under + shifting the lift by any constant: the shift drops out of the velocity. -/ +lemma kineticEnergy_add_const (θ : Time → EuclideanSpace ℝ (Fin 1)) + (c : EuclideanSpace ℝ (Fin 1)) : + S.kineticEnergy (fun t => θ t + c) = S.kineticEnergy θ := by + have hd : ∂ₜ (fun t => θ t + c) = ∂ₜ θ := by + funext s + rw [Time.deriv_eq, Time.deriv_eq, fderiv_add_const] + funext t + simp only [kineticEnergy_eq, hd] + +/-- The energy of the simple pendulum along a lift of the angle is invariant under shifting the + lift by a whole number of turns: A.1 supplies the invariance of the potential energy, and the + velocity is unchanged by a constant shift. -/ +lemma energy_add_int_mul_two_pi (θ : Time → EuclideanSpace ℝ (Fin 1)) (n : ℤ) : + S.energy (fun t => θ t + (n * (2 * Real.pi)) • EuclideanSpace.single 0 1) = + S.energy θ := by + funext t + simp only [energy_eq, S.kineticEnergy_add_const θ _, S.potentialEnergy_add_int_mul_two_pi (θ t) n] + +/-! + +### A.3. Invariance of the equation of motion and its solutions + +Both sides of the equation of motion are invariant under the shift: the angular momentum, +because the shift is constant in time, and the torque, by the invariance of A.1. Smoothness is +likewise unaffected by adding a constant, so being a solution is invariant as well. + +-/ + +/-- A lift of the angle shifted by a whole number of turns satisfies the equation of motion of + the simple pendulum if and only if the lift itself does. -/ +lemma equationOfMotion_add_int_mul_two_pi (θ : Time → EuclideanSpace ℝ (Fin 1)) (n : ℤ) : + S.EquationOfMotion (fun t => θ t + (n * (2 * Real.pi)) • EuclideanSpace.single 0 1) ↔ + S.EquationOfMotion θ := by + have hd : ∂ₜ (fun t => θ t + (n * (2 * Real.pi)) • EuclideanSpace.single 0 1) = ∂ₜ θ := by + funext s + rw [Time.deriv_eq, Time.deriv_eq, fderiv_add_const] + simp only [EquationOfMotion, hd, torque_add_int_mul_two_pi] + +/-- A lift of the angle shifted by a whole number of turns is a solution of the simple pendulum + if and only if the lift itself is. -/ +lemma isSolution_add_int_mul_two_pi (θ : Time → EuclideanSpace ℝ (Fin 1)) (n : ℤ) : + S.IsSolution (fun t => θ t + (n * (2 * Real.pi)) • EuclideanSpace.single 0 1) ↔ + S.IsSolution θ := by + have hcd : ContDiff ℝ ∞ (fun t => θ t + (n * (2 * Real.pi)) • EuclideanSpace.single 0 1) ↔ + ContDiff ℝ ∞ θ := by + constructor + · intro h + have h2 := h.sub (contDiff_const (c := (n * (2 * Real.pi)) • EuclideanSpace.single 0 1)) + simpa using h2 + · exact fun h => h.add contDiff_const + exact and_congr hcd (S.equationOfMotion_add_int_mul_two_pi θ n) + +/-! + +### A.4. The shifted lift describes the same configuration + +Finally the statement giving the previous invariances their meaning: the lift and its shift by +a whole number of turns project to the same point of the configuration space, by the +periodicity of the angular lift `ConfigurationSpace.ofAngle` with period `2π`. + +-/ + +/-- A lift of the angle and its shift by a whole number of turns describe the same + configuration of the simple pendulum. -/ +lemma ofAngle_add_int_mul_two_pi_coord (x : EuclideanSpace ℝ (Fin 1)) (n : ℤ) : + ConfigurationSpace.ofAngle + ((x + (n * (2 * Real.pi)) • EuclideanSpace.single (0 : Fin 1) (1 : ℝ)) 0) = + ConfigurationSpace.ofAngle (x 0) := by + have h0 : (x + (n * (2 * Real.pi)) • EuclideanSpace.single 0 1 : EuclideanSpace ℝ (Fin 1)) 0 = + x 0 + n * (2 * Real.pi) := by + simp + rw [h0] + exact ConfigurationSpace.ofAngle_periodic.int_mul n (x 0) + +end SimplePendulum + +end ClassicalMechanics + +end From 9c33f2ca279fb8f5c2e5aca1f916df44114ff666 Mon Sep 17 00:00:00 2001 From: Owen Parks Date: Wed, 26 Aug 2026 22:50:30 -0700 Subject: [PATCH 14/20] docs(Relativity): correct dimension in Levi-Civita contraction module docstring (#1581) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The module docstring described `ε4` as living in `d = 3`; the file's results (e.g. the full contraction equalling 24 = 4!) are in `d = 4`. Closes #1533 --- Physlib/Relativity/Tensors/LeviCivita/Contractions.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Physlib/Relativity/Tensors/LeviCivita/Contractions.lean b/Physlib/Relativity/Tensors/LeviCivita/Contractions.lean index f736c77d57..f525f6a2f1 100644 --- a/Physlib/Relativity/Tensors/LeviCivita/Contractions.lean +++ b/Physlib/Relativity/Tensors/LeviCivita/Contractions.lean @@ -15,7 +15,7 @@ public import Physlib.Meta.TODO.Basic ## i. Overview This file proves the "epsilon-epsilon" contraction identities for the rank-four Levi-Civita -tensor `leviCivita` (notation `ε4`) in `d = 3`, stated in terms of the standard-basis +tensor `leviCivita` (notation `ε4`) in `d = 4`, stated in terms of the standard-basis components of `ε4` itself (`realLorentzTensor.leviCivita_basis_repr_apply`). The underlying facts about the `generalizedKroneckerDelta` alone, with no From 3405d86dc5e6e45d625e3a8347b152c79fe19cce Mon Sep 17 00:00:00 2001 From: RaunakChhatwal <85747188+RaunakChhatwal@users.noreply.github.com> Date: Thu, 27 Aug 2026 04:46:32 -0500 Subject: [PATCH 15/20] feat(Units): reducible rational arithmetic via Exponent (#1579) * feat(Units): reducible rational arithmetic via Exponent * feat(Units): use Exponent for dimension coordinates * feat(Units): native exponent-tuple representation of dimensions via DimensionBasis * feat(Units): add Exponent coercion API and Exponent-valued dimension powers --- Physlib.lean | 1 + Physlib/Units/Basic.lean | 8 +- Physlib/Units/Dimension.lean | 121 ++++++-- Physlib/Units/Exponent.lean | 284 ++++++++++++++++++ Physlib/Units/ISQBridge.lean | 27 +- Physlib/Units/ISQDimensionBase.lean | 2 + Physlib/Units/LTMCTDimensionBase.lean | 114 ++++--- .../Units/ParametricDimensionExamples.lean | 70 +++-- Physlib/Units/ParametricUnits.lean | 12 +- Physlib/Units/UnitSystem.lean | 3 +- Physlib/Units/WithDim/Basic.lean | 81 +++-- 11 files changed, 558 insertions(+), 165 deletions(-) create mode 100644 Physlib/Units/Exponent.lean diff --git a/Physlib.lean b/Physlib.lean index db65baeb26..0fc9591f14 100644 --- a/Physlib.lean +++ b/Physlib.lean @@ -547,6 +547,7 @@ public import Physlib.Thermodynamics.Temperature.TemperatureUnits public import Physlib.Units.Basic public import Physlib.Units.Dimension public import Physlib.Units.Examples +public import Physlib.Units.Exponent public import Physlib.Units.FDeriv public import Physlib.Units.ISQBridge public import Physlib.Units.ISQDimensionBase diff --git a/Physlib/Units/Basic.lean b/Physlib/Units/Basic.lean index 824aa63355..b618aba6da 100644 --- a/Physlib/Units/Basic.lean +++ b/Physlib/Units/Basic.lean @@ -108,12 +108,10 @@ noncomputable def dimScale (u1 u2 : LTMCTUnitChoices) :Dimension LTMCTDimensionB map_one' := by simp map_mul' d1 d2 := by - simp only [Dimension.length_mul, Rat.cast_add, Dimension.time_mul, Dimension.mass_mul, - Dimension.charge_mul, Dimension.temperature_mul] - repeat rw [rpow_add] + simp only [Dimension.length_mul, Dimension.Exponent.coe_add, Rat.cast_add, Dimension.time_mul, + Dimension.mass_mul, Dimension.charge_mul, Dimension.temperature_mul] + repeat rw [NNReal.rpow_add (by simp)] ring - all_goals - simp lemma dimScale_apply (u1 u2 : LTMCTUnitChoices) (d : Dimension LTMCTDimensionBase) : dimScale u1 u2 d = diff --git a/Physlib/Units/Dimension.lean b/Physlib/Units/Dimension.lean index 202d091237..63e8f10643 100644 --- a/Physlib/Units/Dimension.lean +++ b/Physlib/Units/Dimension.lean @@ -7,6 +7,7 @@ module public import Mathlib.Analysis.Normed.Field.Lemmas public import Mathlib.Tactic.DeriveFintype +public import Physlib.Units.Exponent /-! # Dimension @@ -14,11 +15,12 @@ public import Mathlib.Tactic.DeriveFintype In this module we define the type `Dimension` which carries the dimension of a physical quantity. -A `Dimension B` is parameterised by a *basis* `B` of base dimensions: it assigns a -rational `exponent` to each base dimension `b : B`. The parameterisation is purely in -the dimensional *algebra*: `Dimension B` is a `CommGroup` for every `B` +A `Dimension B` is parameterised by a *basis* `B` of base dimensions equipped with a +`DimensionBasis` representation. Each representation is additively equivalent to assigning an +`Exponent` to every base dimension `b : B`. The parameterisation is purely in +the dimensional *algebra*: `Dimension B` is a `CommGroup` for every represented basis `B` (multiplication adds exponents, inversion negates them), so quantities can be typed by -dimensions over any basis. The commutative-group and `ℚ`-power structure, decidable +dimensions over any basis. The commutative-group, `Exponent`- and `ℚ`-power structures, decidable equality (`DecidableEq`), the base vectors `single b`, and the change-of-basis map `extend` are all generic in `B`. @@ -41,36 +43,73 @@ open NNReal -/ -/-- A dimension over a basis `B` of base dimensions: a rational `exponent` for each - base dimension `b : B`. PhysLib's default basis is `LTMCTDimensionBase`. -/ -structure Dimension (B : Type) where - /-- The exponent of each base dimension. -/ - exponent : B → ℚ +/-- A choice of exponent-tuple representation for a basis `B`. Native addition on `Exponents` +is used for dimension multiplication, while `exponentEquiv` provides the basis-generic API. -/ +class DimensionBasis (B : Type) where + /-- The native tuple of exponents for this basis. -/ + Exponents : Type + /-- The additive structure on native exponent tuples. -/ + [addCommGroup : AddCommGroup Exponents] + /-- Native exponent tuples are additively equivalent to exponent functions on the basis. -/ + exponentEquiv : Exponents ≃+ (B → Dimension.Exponent) + +attribute [instance_reducible, instance] DimensionBasis.addCommGroup + +namespace DimensionBasis + +/-- The function-backed exponent representation for a basis without a specialized tuple. -/ +@[instance_reducible] def pi (B : Type) : DimensionBasis B where + Exponents := B → Dimension.Exponent + addCommGroup := inferInstance + exponentEquiv := AddEquiv.refl _ + +end DimensionBasis + +/-- A dimension over a represented basis `B`. PhysLib's default basis is +`LTMCTDimensionBase`. -/ +structure Dimension (B : Type) [DimensionBasis B] where + /-- The dimension's native exponent tuple. -/ + exponents : DimensionBasis.Exponents B namespace Dimension -variable {B : Type} +variable {B : Type} [DimensionBasis B] + +/-- The exponent of a dimension at a base dimension. -/ +def exponent (d : Dimension B) : B → Exponent := + DimensionBasis.exponentEquiv d.exponents + +/-- Construct a dimension from an exponent function. -/ +def ofFunction (f : B → Exponent) : Dimension B := + ⟨DimensionBasis.exponentEquiv.symm f⟩ + +@[simp] +lemma ofFunction_exponent (f : B → Exponent) (b : B) : (ofFunction f).exponent b = f b := by + simp [ofFunction, exponent] @[ext] lemma ext {d1 d2 : Dimension B} (h : ∀ b, d1.exponent b = d2.exponent b) : d1 = d2 := by cases d1 cases d2 congr + apply DimensionBasis.exponentEquiv.injective funext b exact h b instance : Mul (Dimension B) where - mul d1 d2 := ⟨fun b => d1.exponent b + d2.exponent b⟩ + mul d1 d2 := ⟨d1.exponents + d2.exponents⟩ @[simp] lemma mul_exponent (d1 d2 : Dimension B) (b : B) : - (d1 * d2).exponent b = d1.exponent b + d2.exponent b := rfl + (d1 * d2).exponent b = d1.exponent b + d2.exponent b := by + exact congrFun (map_add DimensionBasis.exponentEquiv d1.exponents d2.exponents) b instance : One (Dimension B) where - one := ⟨fun _ => 0⟩ + one := ⟨0⟩ @[simp] -lemma one_exponent (b : B) : (1 : Dimension B).exponent b = 0 := rfl +lemma one_exponent (b : B) : (1 : Dimension B).exponent b = 0 := by + exact congrFun (map_zero DimensionBasis.exponentEquiv) b instance : CommGroup (Dimension B) where mul_assoc a b c := by @@ -82,16 +121,19 @@ instance : CommGroup (Dimension B) where mul_one a := by ext x simp - inv d := ⟨fun b => -d.exponent b⟩ + inv d := ⟨-d.exponents⟩ inv_mul_cancel a := by - ext x - simp + cases a with + | mk exponents => + change Dimension.mk (-exponents + exponents) = Dimension.mk 0 + rw [neg_add_cancel] mul_comm a b := by ext x simp [add_comm] @[simp] -lemma inv_exponent (d : Dimension B) (b : B) : d⁻¹.exponent b = -d.exponent b := rfl +lemma inv_exponent (d : Dimension B) (b : B) : d⁻¹.exponent b = -d.exponent b := by + exact congrFun (map_neg DimensionBasis.exponentEquiv d.exponents) b @[simp] lemma div_exponent (d1 d2 : Dimension B) (b : B) : @@ -106,11 +148,23 @@ lemma npow_exponent (d : Dimension B) (n : ℕ) (b : B) : | succ n ih => rw [pow_succ, mul_exponent, ih, succ_nsmul] instance : Pow (Dimension B) ℚ where - pow d q := ⟨fun b => d.exponent b * q⟩ + pow d q := ofFunction fun b => d.exponent b * Exponent.ofRat q @[simp] lemma qpow_exponent (d : Dimension B) (q : ℚ) (b : B) : - (d ^ q).exponent b = d.exponent b * q := rfl + (d ^ q).exponent b = d.exponent b * Exponent.ofRat q := by + exact ofFunction_exponent _ _ + +/-- Raising a dimension to an `Exponent` power. Unlike the `ℚ`-valued power, this preserves +reducible arithmetic for concrete fractional exponents. -/ +@[default_instance 10000] +instance : Pow (Dimension B) Exponent where + pow d c := ofFunction fun b => d.exponent b * c + +@[simp] +lemma epow_exponent (d : Dimension B) (c : Exponent) (b : B) : + (d ^ c).exponent b = d.exponent b * c := by + exact ofFunction_exponent _ _ /-- Decidable equality of dimensions over a finite basis `B`. -/ instance [Fintype B] : DecidableEq (Dimension B) := fun d1 d2 => @@ -119,26 +173,26 @@ instance [Fintype B] : DecidableEq (Dimension B) := fun d1 d2 => /-- The base-dimension vector for `b : B`: exponent `1` at `b`, `0` elsewhere. This is the generic analogue of the named generators `L𝓭`, `T𝓭`, … -/ -def single [DecidableEq B] (b : B) : Dimension B := ⟨Pi.single b 1⟩ +def single [DecidableEq B] (b : B) : Dimension B := ofFunction (Pi.single b 1) @[simp] lemma single_exponent [DecidableEq B] (b b' : B) : (single b).exponent b' = if b' = b then 1 else 0 := by - simp only [single, Pi.single_apply] + simp only [single, ofFunction_exponent, Pi.single_apply] /-- Change of basis along a map `f : B → B'` of base dimensions: reindex a dimension over `B` into one over `B'` by placing each exponent at its image. For an embedding `f` (injective) this preserves every exponent (`extend_exponent_apply`), so a dimension in one system re-expresses faithfully in an extending one. -/ -def extend {B' : Type} [Fintype B] [DecidableEq B'] (f : B → B') (d : Dimension B) : - Dimension B' := - ⟨fun b' => ∑ b, if f b = b' then d.exponent b else 0⟩ +def extend {B' : Type} [DimensionBasis B'] [Fintype B] [DecidableEq B'] + (f : B → B') (d : Dimension B) : Dimension B' := + ofFunction fun b' => ∑ b, if f b = b' then d.exponent b else 0 @[simp] -lemma extend_exponent_apply {B' : Type} [Fintype B] [DecidableEq B'] {f : B → B'} - (hf : Function.Injective f) (d : Dimension B) (b : B) : +lemma extend_exponent_apply {B' : Type} [DimensionBasis B'] [Fintype B] [DecidableEq B'] + {f : B → B'} (hf : Function.Injective f) (d : Dimension B) (b : B) : (extend f d).exponent (f b) = d.exponent b := by - simp only [extend] + simp only [extend, ofFunction_exponent] rw [Finset.sum_eq_single b (fun b'' _ hne => by simp [hf.ne hne]) (by simp)] simp @@ -163,13 +217,13 @@ that sends a base dimension to an inequivalent one is *not* expressible as eithe -/ /-- `extend f` packaged as a monoid homomorphism of dimensions. -/ -def extendHom {B' : Type} [Fintype B] [DecidableEq B'] (f : B → B') : +def extendHom {B' : Type} [DimensionBasis B'] [Fintype B] [DecidableEq B'] (f : B → B') : Dimension B →* Dimension B' where toFun := extend f map_one' := by ext b'; simp [extend] map_mul' d1 d2 := by ext b' - simp only [extend, mul_exponent] + simp only [extend, ofFunction_exponent, mul_exponent] rw [← Finset.sum_add_distrib] refine Finset.sum_congr rfl fun b _ => ?_ split_ifs <;> simp @@ -179,7 +233,7 @@ def extendHom {B' : Type} [Fintype B] [DecidableEq B'] (f : B → B') : inverses and rational powers); injectivity makes it a faithful inclusion of the basis `B` into `B'`. Cross-basis dimension injections are produced as `Embedding`s so that dimension-preservation holds by construction. -/ -structure Embedding (B B' : Type) where +structure Embedding (B B' : Type) [DimensionBasis B] [DimensionBasis B'] where /-- The underlying dimension-preserving homomorphism. -/ toHom : Dimension B →* Dimension B' /-- The homomorphism is injective (a faithful embedding). -/ @@ -189,7 +243,7 @@ structure Embedding (B B' : Type) where dimensions. As a `MonoidHom` it is truth-preserving, but it is lossy — it reduces a richer basis `B'` onto a coarser basis `B`, collapsing the base dimensions that `B` does not track. -/ -structure Projection (B' B : Type) where +structure Projection (B' B : Type) [DimensionBasis B'] [DimensionBasis B] where /-- The underlying dimension-preserving homomorphism. -/ toHom : Dimension B' →* Dimension B /-- The homomorphism is surjective (the reduction hits every dimension of `B`). -/ @@ -198,7 +252,8 @@ structure Projection (B' B : Type) where /-- An injective *basis* map `f : B → B'` induces a dimension embedding, via `extend`. This is the label-level case: it sends each base dimension of `B` to a base dimension of `B'`, so it is automatically dimension-preserving and faithful. -/ -def Embedding.ofBasis {B B' : Type} [Fintype B] [DecidableEq B'] +def Embedding.ofBasis {B B' : Type} [DimensionBasis B] [DimensionBasis B'] + [Fintype B] [DecidableEq B'] (f : B → B') (hf : Function.Injective f) : Embedding B B' where toHom := extendHom f inj := by diff --git a/Physlib/Units/Exponent.lean b/Physlib/Units/Exponent.lean new file mode 100644 index 0000000000..e4c03010af --- /dev/null +++ b/Physlib/Units/Exponent.lean @@ -0,0 +1,284 @@ +/- +Copyright (c) 2026 Raunak Chhatwal. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Raunak Chhatwal +-/ +module + +public import Mathlib.Algebra.Field.TransferInstance +public import Mathlib.Algebra.Field.Rat +public import Mathlib.Algebra.Order.Ring.InjSurj +public import Mathlib.Algebra.Order.Ring.Rat +/-! + +# Reducible rational arithmetic for dimension exponents + +This module defines `Exponent`, a wrapper around the rational numbers whose arithmetic is +reducible. This lets concrete arithmetic on dimension exponents hold by definitional equality. + +The corresponding rational operations are irreducible in Lean. Locally unsealing them does not +export their reducibility to downstream modules, while globally changing the reducibility of an +imported declaration requires `allowUnsafeReducibility`. The wrapper instead owns transparent +operations while remaining equivalent to `ℚ`. + +The reducibility guarantee applies to the custom addition, subtraction, multiplication, inversion, +and division below. Other operations supplied by the `Field` instance are transferred from `ℚ`. +In particular, rational scalar multiplication and negative integer powers may require propositional +reasoning rather than `rfl`. + +-/ + +@[expose] public section + +namespace Dimension + +/-! + +## A. Definition + +-/ + +/-- A rational dimension exponent with reducible arithmetic. -/ +structure Exponent where + /-- The rational number represented by the exponent. -/ + toRat : ℚ +deriving DecidableEq + +attribute [coe] Exponent.toRat + +instance : Repr Exponent where + reprPrec x := reprPrec x.toRat + +namespace Exponent + +/-- The equivalence between `Exponent` and the rational numbers. -/ +def equivRat : Exponent ≃ ℚ := + Equiv.mk Exponent.toRat Exponent.mk Eq.refl Eq.refl + +/-- Regard a rational number as a dimension exponent. -/ +def ofRat (q : ℚ) : Exponent := ⟨q⟩ + +@[simp] +lemma ofRat_toRat (q : ℚ) : (ofRat q).toRat = q := rfl + +/-- Fuel-bounded Euclidean algorithm used to make exponent normalization reducible. -/ +def gcdAux : Nat → Nat → Nat → Nat + | 0, _, n => n + | fuel + 1, m, n => if m = 0 then n else gcdAux fuel (n % m) m + +private lemma gcdAux_eq_nat_gcd (fuel m n : Nat) (m_lt_fuel : m < fuel) : + gcdAux fuel m n = Nat.gcd m n := by + induction fuel generalizing m n with + | zero => omega + | succ fuel ih => + rw [gcdAux, Nat.gcd_def] + split + · rfl + · apply ih; have := Nat.mod_lt n (Nat.zero_lt_of_ne_zero ‹m ≠ 0›); omega + +/-- Reducible greatest common divisor used when normalizing an exponent. -/ +def gcd (m n : Nat) : Nat := + gcdAux (m + 1) m n + +/-- The reducible exponent GCD agrees with `Nat.gcd`. -/ +lemma gcd_eq_nat_gcd (m n : Nat) : gcd m n = Nat.gcd m n := by + exact gcdAux_eq_nat_gcd (m + 1) m n (Nat.lt_add_one m) + +/-- Construct an exponent by normalizing a numerator and a nonzero denominator. -/ +def normalize (num : Int) (den : Nat) (den_ne_zero : den ≠ 0) : Exponent := + let g := gcd num.natAbs den + let g_eq : g = num.natAbs.gcd den := gcd_eq_nat_gcd num.natAbs den + ⟨Rat.maybeNormalize num den g + (Rat.normalize.dvd_num g_eq) + (Rat.normalize.dvd_den g_eq) + (Rat.normalize.den_nz den_ne_zero g_eq) + (Rat.normalize.reduced den_ne_zero g_eq)⟩ + +lemma normalize_toRat (num : Int) (den : Nat) (den_ne_zero : den ≠ 0) : + (normalize num den den_ne_zero).toRat = Rat.normalize num den den_ne_zero := by + unfold normalize Rat.normalize + simp only [gcd_eq_nat_gcd] + +/-- The normalized numerator of an exponent. -/ +@[reducible] def num (x : Exponent) : Int := + x.toRat.num + +/-- The normalized denominator of an exponent. -/ +@[reducible] def den (x : Exponent) : Nat := + x.toRat.den + +/-! + +## B. Arithmetic + +-/ + +/-- Reducible addition of dimension exponents. -/ +def add (a b : Exponent) : Exponent := + normalize (a.num * b.den + b.num * a.den) (a.den * b.den) + (Nat.mul_ne_zero a.toRat.den_nz b.toRat.den_nz) + +instance : Add Exponent := Add.mk add + +lemma add_equiv (a b : Exponent) : equivRat (add a b) = equivRat a + equivRat b := by + rw [Rat.add_def] + exact normalize_toRat _ _ _ + +/-- Reducible subtraction of dimension exponents. -/ +def sub (a b : Exponent) : Exponent := + add a ⟨-b.toRat⟩ + +instance : Sub Exponent := Sub.mk sub + +lemma sub_equiv (a b : Exponent) : equivRat (sub a b) = equivRat a - equivRat b := by + rw [sub, add_equiv] + simp [equivRat, sub_eq_add_neg] + +/-- Reducible multiplication of dimension exponents. -/ +def mul (a b : Exponent) : Exponent := + normalize (a.num * b.num) (a.den * b.den) + (Nat.mul_ne_zero a.toRat.den_nz b.toRat.den_nz) + +instance : Mul Exponent := Mul.mk mul + +lemma mul_equiv (a b : Exponent) : equivRat (mul a b) = equivRat a * equivRat b := by + rw [Rat.mul_def] + exact normalize_toRat _ _ _ + +/-- Reducible inversion of a dimension exponent, with `0⁻¹ = 0`. -/ +def inv (a : Exponent) : Exponent := + if ne_zero : a.toRat ≠ 0 then + have num_ne_zero : a.num ≠ 0 := ne_zero ∘ Rat.num_eq_zero.mp + ⟨{ num := a.num.sign * a.den + den := a.num.natAbs + den_nz := by exact Nat.ne_of_gt (Int.natAbs_pos.mpr num_ne_zero) + reduced := by simpa [Int.natAbs_mul, Int.natAbs_sign_of_ne_zero num_ne_zero] + using a.toRat.reduced.symm }⟩ + else a + +instance : Inv Exponent := Inv.mk inv + +lemma inv_equiv (a : Exponent) : equivRat (inv a) = (equivRat a)⁻¹ := by + by_cases ne_zero : a.toRat ≠ 0 + · apply Rat.ext <;> simp [inv, ne_zero, equivRat, Rat.num_inv, Rat.den_inv] + · push Not at ne_zero + apply Rat.ext <;> simp [inv, ne_zero, equivRat] + +/-- Reducible division of dimension exponents. -/ +def div (a b : Exponent) : Exponent := + mul a (inv b) + +instance : Div Exponent := Div.mk div + +lemma div_equiv (a b : Exponent) : equivRat (div a b) = equivRat a / equivRat b := by + rw [div, mul_equiv, inv_equiv, div_eq_mul_inv] + +/-! + +## C. Field structure + +-/ + +instance instField : Field Exponent := by + letI := equivRat.field + apply equivRat.injective.field + · rfl + · rfl + all_goals intros + case add => apply add_equiv + case sub => apply sub_equiv + case inv => apply inv_equiv + case mul => apply mul_equiv + case div => apply div_equiv + all_goals rfl + +/-- The ring equivalence between dimension exponents and rational numbers. -/ +def ringEquivRat : Exponent ≃+* ℚ where + toEquiv := equivRat + map_add' := add_equiv + map_mul' := mul_equiv + +/-- Regard a dimension exponent as a rational number. -/ +instance : Coe Exponent ℚ := ⟨Exponent.toRat⟩ + +@[simp, norm_cast] +lemma coe_inj {a b : Exponent} : (a : ℚ) = b ↔ a = b := + ringEquivRat.injective.eq_iff + +@[simp, norm_cast] +lemma coe_zero : ((0 : Exponent) : ℚ) = 0 := map_zero ringEquivRat + +@[simp, norm_cast] +lemma coe_one : ((1 : Exponent) : ℚ) = 1 := map_one ringEquivRat + +@[simp, norm_cast] +lemma coe_ofNat (n : ℕ) [n.AtLeastTwo] : ((ofNat(n) : Exponent) : ℚ) = ofNat(n) := + map_ofNat ringEquivRat n + +@[simp, norm_cast] +lemma coe_add (a b : Exponent) : ((a + b : Exponent) : ℚ) = a + b := + map_add ringEquivRat a b + +@[simp, norm_cast] +lemma coe_sub (a b : Exponent) : ((a - b : Exponent) : ℚ) = a - b := + map_sub ringEquivRat a b + +@[simp, norm_cast] +lemma coe_neg (a : Exponent) : ((-a : Exponent) : ℚ) = -a := + map_neg ringEquivRat a + +@[simp, norm_cast] +lemma coe_mul (a b : Exponent) : ((a * b : Exponent) : ℚ) = a * b := + map_mul ringEquivRat a b + +@[simp, norm_cast] +lemma coe_inv (a : Exponent) : ((a⁻¹ : Exponent) : ℚ) = (a : ℚ)⁻¹ := + inv_equiv a + +@[simp, norm_cast] +lemma coe_div (a b : Exponent) : ((a / b : Exponent) : ℚ) = (a : ℚ) / b := + div_equiv a b + +instance : LinearOrder Exponent := equivRat.linearOrder + +@[simp, norm_cast] +lemma coe_le_coe {a b : Exponent} : (a : ℚ) ≤ b ↔ a ≤ b := Iff.rfl + +@[simp, norm_cast] +lemma coe_lt_coe {a b : Exponent} : (a : ℚ) < b ↔ a < b := Iff.rfl + +instance : IsStrictOrderedRing Exponent := + Function.Injective.isStrictOrderedRing ringEquivRat + (map_zero ringEquivRat) (map_one ringEquivRat) (map_add ringEquivRat) (map_mul ringEquivRat) + coe_le_coe coe_lt_coe + +instance : CharZero Exponent where + cast_injective _ _ equality := Nat.cast_injective <| congrArg equivRat equality + +-- These regressions pin the field structure to the reducible operations above. +lemma add_eq_instField_add : add = instField.add := rfl +lemma sub_eq_instField_sub : sub = instField.sub := rfl +lemma inv_eq_instField_inv : inv = instField.inv := rfl +lemma mul_eq_instField_mul : mul = instField.mul := rfl +lemma div_eq_instField_div : div = instField.div := rfl + +/-! + +## D. Definitional equality tests + +-/ + +lemma tuple_arithmetic_defeq : + let Length : Exponent × Exponent := (1, 0) + let Time : Exponent × Exponent := (0, 1) + let Speed := Length - Time + Length = Time + Speed := rfl + +lemma rational_arithmetic_defeq : + ((2 / 3 + 5 / 7) * (11 / 13 - 1 / 2) : Exponent) = 87 / 182 := rfl + +lemma inverse_arithmetic_defeq : ((-3 / 4 : Exponent)⁻¹ + 5 / 6) = -1 / 2 := rfl + +end Dimension.Exponent + +end diff --git a/Physlib/Units/ISQBridge.lean b/Physlib/Units/ISQBridge.lean index 1f15d59265..c005cd31ff 100644 --- a/Physlib/Units/ISQBridge.lean +++ b/Physlib/Units/ISQBridge.lean @@ -42,14 +42,14 @@ namespace Dimension send PhysLib's charge generator to the derived ISQ charge `I · T` (the current exponent is the charge exponent, and the time exponent absorbs it). -/ def toISQFun (d : Dimension LTMCTDimensionBase) : Dimension ISQDimensionBase := - ⟨fun + ofFunction fun | .length => d.exponent .length | .mass => d.exponent .mass | .time => d.exponent .time + d.exponent .charge | .current => d.exponent .charge | .temperature => d.exponent .temperature | .amount => 0 - | .luminousIntensity => 0⟩ + | .luminousIntensity => 0 /-- The dimension-preserving embedding of PhysLib dimensions into the ISQ dimensions. -/ def toISQHom : Dimension LTMCTDimensionBase →* Dimension ISQDimensionBase where @@ -57,7 +57,7 @@ def toISQHom : Dimension LTMCTDimensionBase →* Dimension ISQDimensionBase wher map_one' := by ext b; cases b <;> simp [toISQFun] map_mul' d1 d2 := by ext b - cases b <;> simp only [toISQFun, mul_exponent] + cases b <;> simp only [toISQFun, ofFunction_exponent, mul_exponent] all_goals ring /-- `toISQHom` applied to a dimension is `toISQFun`. -/ @@ -67,12 +67,12 @@ lemma toISQHom_apply (d : Dimension LTMCTDimensionBase) : toISQHom d = toISQFun electric current as charge/time (the charge exponent is the current exponent, and the time exponent subtracts it), and drop amount of substance and luminous intensity. -/ def fromISQFun (d : Dimension ISQDimensionBase) : Dimension LTMCTDimensionBase := - ⟨fun + ofFunction fun | .length => d.exponent .length | .time => d.exponent .time - d.exponent .current | .mass => d.exponent .mass | .charge => d.exponent .current - | .temperature => d.exponent .temperature⟩ + | .temperature => d.exponent .temperature /-- The truth-preserving reduction of ISQ dimensions onto PhysLib's. -/ def fromISQHom : Dimension ISQDimensionBase →* Dimension LTMCTDimensionBase where @@ -80,7 +80,7 @@ def fromISQHom : Dimension ISQDimensionBase →* Dimension LTMCTDimensionBase wh map_one' := by ext b; cases b <;> simp [fromISQFun] map_mul' d1 d2 := by ext b - cases b <;> simp only [fromISQFun, mul_exponent] + cases b <;> simp only [fromISQFun, ofFunction_exponent, mul_exponent] all_goals ring /-- `fromISQHom` applied to a dimension is `fromISQFun`. -/ @@ -92,7 +92,7 @@ lemma fromISQHom_comp_toISQHom : fromISQHom.comp toISQHom = MonoidHom.id (Dimension LTMCTDimensionBase) := by refine MonoidHom.ext fun d => Dimension.ext fun b => ?_ cases b <;> simp only [MonoidHom.comp_apply, MonoidHom.id_apply, toISQHom_apply, - fromISQHom_apply, fromISQFun, toISQFun] + fromISQHom_apply, fromISQFun, toISQFun, ofFunction_exponent] all_goals ring /-- `toISQHom` is injective: PhysLib dimensions include faithfully into ISQ. -/ @@ -104,15 +104,15 @@ lemma toISQHom_injective : Function.Injective toISQHom := by simpa only [toISQHom_apply] using hb ext b cases b with - | length => simpa only [toISQFun] using key .length + | length => simpa only [toISQFun, ofFunction_exponent] using key .length | time => have ht := key .time have hc := key .current - simp only [toISQFun] at ht hc + simp only [toISQFun, ofFunction_exponent] at ht hc linarith - | mass => simpa only [toISQFun] using key .mass - | charge => simpa only [toISQFun] using key .current - | temperature => simpa only [toISQFun] using key .temperature + | mass => simpa only [toISQFun, ofFunction_exponent] using key .mass + | charge => simpa only [toISQFun, ofFunction_exponent] using key .current + | temperature => simpa only [toISQFun, ofFunction_exponent] using key .temperature /-- `fromISQHom` is surjective: every PhysLib dimension is the reduction of some ISQ dimension (namely its own embedding). -/ @@ -141,7 +141,6 @@ lemma isqToLTMCT_comp_ltmctToISQ : maps to the *derived* ISQ charge `I · T`. -/ lemma toISQHom_C𝓭 : toISQHom C𝓭 = ISQDimensionBase.charge := by ext b - cases b <;> simp [toISQHom_apply, toISQFun, C𝓭, ofLTMCTDimensionBase, - ISQDimensionBase.charge, single_exponent] + cases b <;> simp [toISQHom_apply, toISQFun, C𝓭, ISQDimensionBase.charge, single_exponent] end Dimension diff --git a/Physlib/Units/ISQDimensionBase.lean b/Physlib/Units/ISQDimensionBase.lean index 917b348820..dd955080ca 100644 --- a/Physlib/Units/ISQDimensionBase.lean +++ b/Physlib/Units/ISQDimensionBase.lean @@ -62,6 +62,8 @@ instance : Fintype ISQDimensionBase where elems := {.length, .mass, .time, .current, .temperature, .amount, .luminousIntensity} complete := fun x => by cases x <;> decide +instance : DimensionBasis ISQDimensionBase := DimensionBasis.pi _ + namespace ISQDimensionBase /-- The ISQ has seven base quantities. -/ diff --git a/Physlib/Units/LTMCTDimensionBase.lean b/Physlib/Units/LTMCTDimensionBase.lean index be57d397a0..c2a5344709 100644 --- a/Physlib/Units/LTMCTDimensionBase.lean +++ b/Physlib/Units/LTMCTDimensionBase.lean @@ -44,10 +44,29 @@ inductive LTMCTDimensionBase where | temperature deriving DecidableEq +namespace LTMCTDimensionBase + instance : Fintype LTMCTDimensionBase where elems := {.length, .time, .mass, .charge, .temperature} complete := fun x => by cases x <;> decide +open Dimension in +/-- The fixed five-component exponent tuple for PhysLib's default dimension basis. -/ +abbrev Exponents := Exponent × Exponent × Exponent × Exponent × Exponent + +instance : DimensionBasis LTMCTDimensionBase where + Exponents := Exponents + addCommGroup := inferInstance + exponentEquiv := + { toFun := fun ⟨l, t, m, c, temp⟩ => fun + | .length => l | .time => t | .mass => m | .charge => c | .temperature => temp + invFun f := ⟨f .length, f .time, f .mass, f .charge, f .temperature⟩ + left_inv e := by rcases e; rfl + right_inv f := by funext b; cases b <;> rfl + map_add' _ _ := by funext b; cases b <;> rfl } + +end LTMCTDimensionBase + namespace Dimension /-! @@ -60,44 +79,56 @@ the familiar `.length`, `.time`, `.mass`, `.charge`, `.temperature` API is avail -/ /-- The length exponent of a `LTMCTDimensionBase` dimension. -/ -def length (d : Dimension LTMCTDimensionBase) : ℚ := d.exponent .length +def length (d : Dimension LTMCTDimensionBase) : Exponent := d.exponents.1 /-- The time exponent of a `LTMCTDimensionBase` dimension. -/ -def time (d : Dimension LTMCTDimensionBase) : ℚ := d.exponent .time +def time (d : Dimension LTMCTDimensionBase) : Exponent := d.exponents.2.1 /-- The mass exponent of a `LTMCTDimensionBase` dimension. -/ -def mass (d : Dimension LTMCTDimensionBase) : ℚ := d.exponent .mass +def mass (d : Dimension LTMCTDimensionBase) : Exponent := d.exponents.2.2.1 /-- The charge exponent of a `LTMCTDimensionBase` dimension. -/ -def charge (d : Dimension LTMCTDimensionBase) : ℚ := d.exponent .charge +def charge (d : Dimension LTMCTDimensionBase) : Exponent := d.exponents.2.2.2.1 /-- The temperature exponent of a `LTMCTDimensionBase` dimension. -/ -def temperature (d : Dimension LTMCTDimensionBase) : ℚ := d.exponent .temperature +def temperature (d : Dimension LTMCTDimensionBase) : Exponent := d.exponents.2.2.2.2 + +@[simp] +lemma exponent_length (d : Dimension LTMCTDimensionBase) : d.exponent .length = d.length := rfl + +@[simp] +lemma exponent_time (d : Dimension LTMCTDimensionBase) : d.exponent .time = d.time := rfl + +@[simp] +lemma exponent_mass (d : Dimension LTMCTDimensionBase) : d.exponent .mass = d.mass := rfl + +@[simp] +lemma exponent_charge (d : Dimension LTMCTDimensionBase) : d.exponent .charge = d.charge := rfl + +@[simp] +lemma exponent_temperature (d : Dimension LTMCTDimensionBase) : + d.exponent .temperature = d.temperature := rfl /-- Build a `LTMCTDimensionBase` dimension from its five exponents, in the order `⟨length, time, mass, charge, temperature⟩`. -/ -def ofLTMCTDimensionBase (length time mass charge temperature : ℚ) : Dimension LTMCTDimensionBase := - ⟨fun - | .length => length - | .time => time - | .mass => mass - | .charge => charge - | .temperature => temperature⟩ +def ofLTMCTDimensionBase (length time mass charge temperature : Exponent) : + Dimension LTMCTDimensionBase := + ⟨(length, time, mass, charge, temperature)⟩ @[simp] -lemma ofLTMCTDimensionBase_length (l t m c θ : ℚ) : +lemma ofLTMCTDimensionBase_length (l t m c θ : Exponent) : (ofLTMCTDimensionBase l t m c θ).length = l := rfl @[simp] -lemma ofLTMCTDimensionBase_time (l t m c θ : ℚ) : +lemma ofLTMCTDimensionBase_time (l t m c θ : Exponent) : (ofLTMCTDimensionBase l t m c θ).time = t := rfl @[simp] -lemma ofLTMCTDimensionBase_mass (l t m c θ : ℚ) : +lemma ofLTMCTDimensionBase_mass (l t m c θ : Exponent) : (ofLTMCTDimensionBase l t m c θ).mass = m := rfl @[simp] -lemma ofLTMCTDimensionBase_charge (l t m c θ : ℚ) : +lemma ofLTMCTDimensionBase_charge (l t m c θ : Exponent) : (ofLTMCTDimensionBase l t m c θ).charge = c := rfl @[simp] -lemma ofLTMCTDimensionBase_temperature (l t m c θ : ℚ) : +lemma ofLTMCTDimensionBase_temperature (l t m c θ : Exponent) : (ofLTMCTDimensionBase l t m c θ).temperature = θ := rfl @[simp] @@ -149,49 +180,53 @@ lemma inv_charge (d : Dimension LTMCTDimensionBase) : d⁻¹.charge = -d.charge @[simp] lemma inv_temperature (d : Dimension LTMCTDimensionBase) : d⁻¹.temperature = -d.temperature := rfl +private lemma component_npow (component : Dimension LTMCTDimensionBase → Exponent) + (b : LTMCTDimensionBase) (h : ∀ d, d.exponent b = component d) + (d : Dimension LTMCTDimensionBase) (n : ℕ) : + component (d ^ n) = n • component d := by + calc + component (d ^ n) = (d ^ n).exponent b := (h _).symm + _ = n • d.exponent b := npow_exponent d n b + _ = n • component d := congrArg (n • ·) (h _) + @[simp] lemma div_length (d1 d2 : Dimension LTMCTDimensionBase) : - (d1 / d2).length = d1.length - d2.length := by - simp only [length, div_exponent] + (d1 / d2).length = d1.length - d2.length := rfl @[simp] -lemma div_time (d1 d2 : Dimension LTMCTDimensionBase) : (d1 / d2).time = d1.time - d2.time := by - simp only [time, div_exponent] +lemma div_time (d1 d2 : Dimension LTMCTDimensionBase) : (d1 / d2).time = d1.time - d2.time := rfl @[simp] -lemma div_mass (d1 d2 : Dimension LTMCTDimensionBase) : (d1 / d2).mass = d1.mass - d2.mass := by - simp only [mass, div_exponent] +lemma div_mass (d1 d2 : Dimension LTMCTDimensionBase) : (d1 / d2).mass = d1.mass - d2.mass := rfl @[simp] lemma div_charge (d1 d2 : Dimension LTMCTDimensionBase) : - (d1 / d2).charge = d1.charge - d2.charge := by - simp only [charge, div_exponent] + (d1 / d2).charge = d1.charge - d2.charge := rfl @[simp] lemma div_temperature (d1 d2 : Dimension LTMCTDimensionBase) : - (d1 / d2).temperature = d1.temperature - d2.temperature := by - simp only [temperature, div_exponent] + (d1 / d2).temperature = d1.temperature - d2.temperature := rfl @[simp] lemma npow_length (d : Dimension LTMCTDimensionBase) (n : ℕ) : (d ^ n).length = n • d.length := by - simp only [length, npow_exponent] + exact component_npow length .length exponent_length d n @[simp] lemma npow_time (d : Dimension LTMCTDimensionBase) (n : ℕ) : (d ^ n).time = n • d.time := by - simp only [time, npow_exponent] + exact component_npow time .time exponent_time d n @[simp] lemma npow_mass (d : Dimension LTMCTDimensionBase) (n : ℕ) : (d ^ n).mass = n • d.mass := by - simp only [mass, npow_exponent] + exact component_npow mass .mass exponent_mass d n @[simp] lemma npow_charge (d : Dimension LTMCTDimensionBase) (n : ℕ) : (d ^ n).charge = n • d.charge := by - simp only [charge, npow_exponent] + exact component_npow charge .charge exponent_charge d n @[simp] lemma npow_temperature (d : Dimension LTMCTDimensionBase) (n : ℕ) : (d ^ n).temperature = n • d.temperature := by - simp only [temperature, npow_exponent] + exact component_npow temperature .temperature exponent_temperature d n /-- The dimension corresponding to length. -/ def L𝓭 : Dimension LTMCTDimensionBase := ofLTMCTDimensionBase 1 0 0 0 0 @@ -247,19 +282,14 @@ corresponding base dimension, exhibiting them as instances of the basis-generic -/ -lemma L𝓭_eq_single : L𝓭 = single .length := by - ext b; cases b <;> simp [L𝓭, ofLTMCTDimensionBase, single_exponent] +lemma L𝓭_eq_single : L𝓭 = single .length := rfl -lemma T𝓭_eq_single : T𝓭 = single .time := by - ext b; cases b <;> simp [T𝓭, ofLTMCTDimensionBase, single_exponent] +lemma T𝓭_eq_single : T𝓭 = single .time := rfl -lemma M𝓭_eq_single : M𝓭 = single .mass := by - ext b; cases b <;> simp [M𝓭, ofLTMCTDimensionBase, single_exponent] +lemma M𝓭_eq_single : M𝓭 = single .mass := rfl -lemma C𝓭_eq_single : C𝓭 = single .charge := by - ext b; cases b <;> simp [C𝓭, ofLTMCTDimensionBase, single_exponent] +lemma C𝓭_eq_single : C𝓭 = single .charge := rfl -lemma Θ𝓭_eq_single : Θ𝓭 = single .temperature := by - ext b; cases b <;> simp [Θ𝓭, ofLTMCTDimensionBase, single_exponent] +lemma Θ𝓭_eq_single : Θ𝓭 = single .temperature := rfl end Dimension diff --git a/Physlib/Units/ParametricDimensionExamples.lean b/Physlib/Units/ParametricDimensionExamples.lean index 049eb96cc7..a10701a526 100644 --- a/Physlib/Units/ParametricDimensionExamples.lean +++ b/Physlib/Units/ParametricDimensionExamples.lean @@ -17,28 +17,23 @@ illustrates two consequences. A recurring question is how to compare a quantity of dimension `length` with a product of a quantity of dimension `length / time` and a quantity of dimension -`time`. The two dimensions are *equal*, but this equality is a **group -cancellation law** on the rational exponents — it holds *propositionally*, never -*definitionally*: - -* `(L𝓭 / T𝓭) * T𝓭 = L𝓭` cannot be closed by `rfl`: cancellation is not a - reduction rule. -* nor by `decide`: the exponents are rational, so the kernel has nothing to - evaluate. - -Consequently `WithDim ((L𝓭 / T𝓭) * T𝓭) ℝ` and `WithDim L𝓭 ℝ` are genuinely -different types, and a bare `x = v * t` is a type error. The bridge is -`WithDim.cast`, whose default argument discharges the propositional dimension -equality automatically, so the comparison is a one-liner. This is not a -limitation of the representation: no representation of `Dimension` makes the -equality definitional, so a cast on a proven equality is the correct idiom. +`time`. In the fixed-tuple representation of `LTMCTDimensionBase`, reducible exponent +arithmetic makes this concrete cancellation a definitional equality: + +* `(L𝓭 / T𝓭) * T𝓭 = L𝓭` is closed by `rfl`. +* `WithDim ((L𝓭 / T𝓭) * T𝓭) ℝ` and `WithDim L𝓭 ℝ` are therefore definitionally + equal types. + +Consequently a bare `x = v * t` is well-typed for these concrete dimensions. For a +representation where the same cancellation holds only propositionally, `WithDim.cast` +bridges the two dimension-indexed types. ## A non-standard basis -Because `Dimension` is parametric, the same dimensional algebra and the same -`cast`-based comparison are available over *any* basis — not just the physical -`LTMCTDimensionBase`. The unit-scaling layer (`LTMCTUnitChoices`, `dimScale`) is not needed -for either the algebra or the comparison, so neither is referenced here. +Because `Dimension` is parametric, the same dimensional algebra and `cast`-based +comparison are available over any represented basis, not just the physical +`LTMCTDimensionBase`. The unit-scaling layer (`LTMCTUnitChoices`, `dimScale`) is not +needed for either the algebra or the comparison, so neither is referenced here. This module is illustrative and should not be imported by other modules. @@ -50,20 +45,29 @@ open Dimension namespace ParametricDimensionExamples -/-- The dimension equality `(length / time) · time = length` holds -propositionally, by cancellation of the rational exponents. -/ -example : (L𝓭 / T𝓭) * T𝓭 = L𝓭 := by ext; simp +/-- The concrete dimension equality `(length / time) · time = length` holds by +definitional equality. -/ +example : (L𝓭 / T𝓭) * T𝓭 = L𝓭 := rfl -/-- The dimensions are equal, but the two `WithDim` *types* are not -definitionally equal, so `WithDim.cast` bridges them. Its default argument proves -`(L𝓭 / T𝓭) * T𝓭 = L𝓭` with no manual proof. -/ -noncomputable example (v : WithDim (L𝓭 / T𝓭) ℝ) (t : WithDim T𝓭 ℝ) : WithDim L𝓭 ℝ := - (v * t).cast +/-- The two concrete `WithDim` types are definitionally equal, so multiplication has +the required result type without a cast. -/ +example (v : WithDim (L𝓭 / T𝓭) ℝ) (t : WithDim T𝓭 ℝ) : WithDim L𝓭 ℝ := + v * t -/-- The end-to-end comparison: a length equals a velocity times a time, once the -product is cast to the length dimension. -/ +/-- The end-to-end comparison: a length equals a velocity times a time directly. -/ example (x : WithDim L𝓭 ℝ) (v : WithDim (L𝓭 / T𝓭) ℝ) (t : WithDim T𝓭 ℝ) : Prop := - x = (v * t).cast + x = v * t + +/-- Two half-powers of length multiply definitionally to length. The unannotated exponent also +regresses the default away from natural-number division. -/ +example : (L𝓭 ^ (1 / 2)) * (L𝓭 ^ (1 / 2)) = L𝓭 := rfl + +/-- Reducible dimension powers also cancel for non-half fractional exponents. -/ +example : (L𝓭 ^ (2 / 3 : Exponent)) * (L𝓭 ^ (1 / 3 : Exponent)) = L𝓭 := rfl + +/-- Quantities carrying half-powers of length multiply directly to a length. -/ +example (a b : WithDim (L𝓭 ^ (1 / 2)) ℝ) : WithDim L𝓭 ℝ := + a * b /-! ## The same comparison over a non-standard basis @@ -79,11 +83,13 @@ inductive Info /-- The symbol base dimension. -/ | symbol +instance : DimensionBasis Info := DimensionBasis.pi _ + /-- The `bit` base dimension. -/ -def bitDim : Dimension Info := ⟨fun | .bit => 1 | .symbol => 0⟩ +def bitDim : Dimension Info := Dimension.ofFunction fun | .bit => 1 | .symbol => 0 /-- The `symbol` base dimension. -/ -def symbolDim : Dimension Info := ⟨fun | .bit => 0 | .symbol => 1⟩ +def symbolDim : Dimension Info := Dimension.ofFunction fun | .bit => 0 | .symbol => 1 /-- Cancellation works identically over the non-standard basis. -/ example : (bitDim / symbolDim) * symbolDim = bitDim := by ext; simp diff --git a/Physlib/Units/ParametricUnits.lean b/Physlib/Units/ParametricUnits.lean index 92337a5c1d..17ebf2cfc5 100644 --- a/Physlib/Units/ParametricUnits.lean +++ b/Physlib/Units/ParametricUnits.lean @@ -54,28 +54,30 @@ lemma ratio_ne_zero (u1 u2 : UnitScale B) (b : B) : u1.scale b / u2.scale b ≠ dimension `d` rescales by `∏ b, (u1 b / u2 b) ^ d.exponent b` when changing the unit choice from `u1` to `u2`. This is the basis-generic form of `LTMCTUnitChoices.dimScale`. -/ -noncomputable def dimScale [Fintype B] (u1 u2 : UnitScale B) : Dimension B →* ℝ≥0 where +noncomputable def dimScale [DimensionBasis B] [Fintype B] + (u1 u2 : UnitScale B) : Dimension B →* ℝ≥0 where toFun d := ∏ b, (u1.scale b / u2.scale b) ^ (d.exponent b : ℝ) map_one' := by simp map_mul' d1 d2 := by - simp only [Dimension.mul_exponent, Rat.cast_add] + simp only [Dimension.mul_exponent, Dimension.Exponent.coe_add, Rat.cast_add] rw [← Finset.prod_mul_distrib] exact Finset.prod_congr rfl fun b _ => NNReal.rpow_add (u1.ratio_ne_zero u2 b) _ _ @[simp] -lemma dimScale_self [Fintype B] (u : UnitScale B) (d : Dimension B) : +lemma dimScale_self [DimensionBasis B] [Fintype B] (u : UnitScale B) (d : Dimension B) : dimScale u u d = 1 := by simp only [dimScale, MonoidHom.coe_mk, OneHom.coe_mk] refine Finset.prod_eq_one fun b _ => ?_ rw [div_self (u.scale_pos b).ne', NNReal.one_rpow] @[simp] -lemma dimScale_one [Fintype B] (u1 u2 : UnitScale B) : +lemma dimScale_one [DimensionBasis B] [Fintype B] (u1 u2 : UnitScale B) : dimScale u1 u2 1 = 1 := map_one _ /-- The scaling is transitive (a cocycle in the unit choices). -/ -lemma dimScale_transitive [Fintype B] (u1 u2 u3 : UnitScale B) (d : Dimension B) : +lemma dimScale_transitive [DimensionBasis B] [Fintype B] + (u1 u2 u3 : UnitScale B) (d : Dimension B) : dimScale u1 u2 d * dimScale u2 u3 d = dimScale u1 u3 d := by simp only [dimScale, MonoidHom.coe_mk, OneHom.coe_mk, ← Finset.prod_mul_distrib] refine Finset.prod_congr rfl fun b _ => ?_ diff --git a/Physlib/Units/UnitSystem.lean b/Physlib/Units/UnitSystem.lean index b593bfd616..506d4fdb08 100644 --- a/Physlib/Units/UnitSystem.lean +++ b/Physlib/Units/UnitSystem.lean @@ -229,6 +229,7 @@ lemma dimScale_eq_toScale_dimScale (u1 u2 : LTMCTUnitChoices) (d : Dimension LTM rw [dimScale_apply, length_ratio, time_ratio, mass_ratio, charge_ratio, temperature_ratio, UnitScale.dimScale, MonoidHom.coe_mk, OneHom.coe_mk, prod_univ_LTMCTDimensionBase] simp only [Dimension.length, Dimension.time, Dimension.mass, Dimension.charge, - Dimension.temperature] + Dimension.temperature, Dimension.exponent_length, Dimension.exponent_time, + Dimension.exponent_mass, Dimension.exponent_charge, Dimension.exponent_temperature] end LTMCTUnitChoices diff --git a/Physlib/Units/WithDim/Basic.lean b/Physlib/Units/WithDim/Basic.lean index 3b06a9dca6..0b02c4ab3f 100644 --- a/Physlib/Units/WithDim/Basic.lean +++ b/Physlib/Units/WithDim/Basic.lean @@ -26,14 +26,15 @@ routes through `LTMCTUnitChoices.dimScale`, is provided for the standard basis open NNReal /-- The type `M` carrying an instance of a dimension `d`. -/ -structure WithDim {B : Type} (d : Dimension B) (M : Type) where +structure WithDim {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) where /-- The underlying value of `M`. -/ val : M namespace WithDim @[ext] -lemma ext {B : Type} {d : Dimension B} {M} (x1 x2 : WithDim d M) (h : x1.val = x2.val) : +lemma ext {B : Type} [DimensionBasis B] {d : Dimension B} {M} + (x1 x2 : WithDim d M) (h : x1.val = x2.val) : x1 = x2 := by cases x1 cases x2 @@ -50,50 +51,58 @@ lemma dim_apply (d : Dimension LTMCTDimensionBase) (M : Type) : ## Inherited instances -/ -instance {B : Type} (d : Dimension B) (M : Type) [Inhabited M] : Inhabited (WithDim d M) where +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [Inhabited M] : + Inhabited (WithDim d M) where default := ⟨default⟩ -instance {B : Type} (d : Dimension B) (M : Type) [Zero M] : Zero (WithDim d M) where +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [Zero M] : + Zero (WithDim d M) where zero := ⟨0⟩ @[simp] -lemma val_zero {B : Type} {d : Dimension B} {M : Type} [Zero M] : +lemma val_zero {B : Type} [DimensionBasis B] {d : Dimension B} {M : Type} [Zero M] : (0 : WithDim d M).val = 0 := rfl -instance {B : Type} (d : Dimension B) (M : Type) [Add M] : Add (WithDim d M) where +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [Add M] : + Add (WithDim d M) where add m1 m2 := ⟨m1.val + m2.val⟩ @[simp] -lemma val_add {B : Type} {d : Dimension B} {M : Type} [Add M] (m1 m2 : WithDim d M) : +lemma val_add {B : Type} [DimensionBasis B] {d : Dimension B} {M : Type} [Add M] + (m1 m2 : WithDim d M) : (m1 + m2).val = m1.val + m2.val := rfl -instance {B : Type} (d : Dimension B) (M : Type) [Neg M] : Neg (WithDim d M) where +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [Neg M] : + Neg (WithDim d M) where neg m := ⟨-m.val⟩ @[simp] -lemma val_neg {B : Type} {d : Dimension B} {M : Type} [Neg M] (m : WithDim d M) : +lemma val_neg {B : Type} [DimensionBasis B] {d : Dimension B} {M : Type} [Neg M] + (m : WithDim d M) : (-m).val = -m.val := rfl -instance {B : Type} (d : Dimension B) (M : Type) [Sub M] : Sub (WithDim d M) where +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [Sub M] : + Sub (WithDim d M) where sub m1 m2 := ⟨m1.val - m2.val⟩ @[simp] -lemma val_sub {B : Type} {d : Dimension B} {M : Type} [Sub M] (m1 m2 : WithDim d M) : +lemma val_sub {B : Type} [DimensionBasis B] {d : Dimension B} {M : Type} [Sub M] + (m1 m2 : WithDim d M) : (m1 - m2).val = m1.val - m2.val := rfl -instance {B : Type} (d : Dimension B) (M : Type) [AddSemigroup M] : +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [AddSemigroup M] : AddSemigroup (WithDim d M) where add_assoc m1 m2 m3 := by ext simp [add_assoc] -instance {B : Type} (d : Dimension B) (M : Type) [AddCommSemigroup M] : +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [AddCommSemigroup M] : AddCommSemigroup (WithDim d M) where add_comm m1 m2 := by ext simp [add_comm] -instance {B : Type} (d : Dimension B) (M : Type) [AddMonoid M] : +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [AddMonoid M] : AddMonoid (WithDim d M) where zero_add m := by ext @@ -103,13 +112,13 @@ instance {B : Type} (d : Dimension B) (M : Type) [AddMonoid M] : simp [add_zero] nsmul := nsmulRec -instance {B : Type} (d : Dimension B) (M : Type) [AddCommMonoid M] : +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [AddCommMonoid M] : AddCommMonoid (WithDim d M) where add_comm m1 m2 := by ext simp [add_comm] -instance {B : Type} (d : Dimension B) (M : Type) [AddGroup M] : +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [AddGroup M] : AddGroup (WithDim d M) where sub_eq_add_neg m1 m2 := by ext @@ -119,27 +128,31 @@ instance {B : Type} (d : Dimension B) (M : Type) [AddGroup M] : simp [neg_add_cancel] zsmul := zsmulRec -instance {B : Type} (d : Dimension B) (M : Type) [AddCommGroup M] : +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [AddCommGroup M] : AddCommGroup (WithDim d M) where add_comm m1 m2 := by ext simp [add_comm] -instance {B : Type} (d : Dimension B) (M : Type) [LE M] : LE (WithDim d M) where +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [LE M] : + LE (WithDim d M) where le m1 m2 := m1.val ≤ m2.val @[simp] -lemma le_def {B : Type} {d : Dimension B} {M : Type} [LE M] (m1 m2 : WithDim d M) : +lemma le_def {B : Type} [DimensionBasis B] {d : Dimension B} {M : Type} [LE M] + (m1 m2 : WithDim d M) : m1 ≤ m2 ↔ m1.val ≤ m2.val := Iff.rfl -instance {B : Type} (d : Dimension B) (M : Type) [LT M] : LT (WithDim d M) where +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [LT M] : + LT (WithDim d M) where lt m1 m2 := m1.val < m2.val @[simp] -lemma lt_def {B : Type} {d : Dimension B} {M : Type} [LT M] (m1 m2 : WithDim d M) : +lemma lt_def {B : Type} [DimensionBasis B] {d : Dimension B} {M : Type} [LT M] + (m1 m2 : WithDim d M) : m1 < m2 ↔ m1.val < m2.val := Iff.rfl -instance {B : Type} (d : Dimension B) (M : Type) [Preorder M] : +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [Preorder M] : Preorder (WithDim d M) where le_refl m := by exact le_refl m.val @@ -150,13 +163,13 @@ instance {B : Type} (d : Dimension B) (M : Type) [Preorder M] : change m1.val < m2.val ↔ m1.val ≤ m2.val ∧ ¬ m2.val ≤ m1.val exact lt_iff_le_not_ge -instance {B : Type} (d : Dimension B) (M : Type) [PartialOrder M] : +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [PartialOrder M] : PartialOrder (WithDim d M) where le_antisymm m1 m2 h12 h21 := by ext exact le_antisymm h12 h21 -instance {B : Type} (d : Dimension B) (M : Type) [MulAction ℝ≥0 M] : +instance {B : Type} [DimensionBasis B] (d : Dimension B) (M : Type) [MulAction ℝ≥0 M] : MulAction ℝ≥0 (WithDim d M) where smul a m := ⟨a • m.val⟩ one_smul m := ext _ _ (one_smul ℝ≥0 m.val) @@ -165,15 +178,15 @@ instance {B : Type} (d : Dimension B) (M : Type) [MulAction ℝ≥0 M] : exact mul_smul a b m.val @[simp] -lemma smul_val {B : Type} {d : Dimension B} {M : Type} [MulAction ℝ≥0 M] +lemma smul_val {B : Type} [DimensionBasis B] {d : Dimension B} {M : Type} [MulAction ℝ≥0 M] (a : ℝ≥0) (m : WithDim d M) : (a • m).val = a • m.val := rfl -instance {B : Type} {d1 d2 : Dimension B} : +instance {B : Type} [DimensionBasis B] {d1 d2 : Dimension B} : HMul (WithDim d1 ℝ) (WithDim d2 ℝ) (WithDim (d1 * d2) ℝ) where hMul m1 m2 := ⟨m1.val * m2.val⟩ -lemma withDim_hMul_val {B : Type} {d1 d2 : Dimension B} +lemma withDim_hMul_val {B : Type} [DimensionBasis B] {d1 d2 : Dimension B} (m1 : WithDim d1 ℝ) (m2 : WithDim d2 ℝ) : (m1 * m2).val = m1.val * m2.val := rfl @@ -192,13 +205,14 @@ instance {d1 d2 : Dimension LTMCTDimensionBase} : open UnitDependent @[simp] -lemma val_mul_eq_mul {B : Type} {d1 d2 : Dimension B} +lemma val_mul_eq_mul {B : Type} [DimensionBasis B] {d1 d2 : Dimension B} (m1 : WithDim d1 ℝ) (m2 : WithDim d2 ℝ) : m1.val * m2.val = (m1 * m2).val := by simp only [withDim_hMul_val] @[simp] -lemma val_pow_two_eq_mul {B : Type} {d1 : Dimension B} (m1 : WithDim d1 ℝ) : +lemma val_pow_two_eq_mul {B : Type} [DimensionBasis B] {d1 : Dimension B} + (m1 : WithDim d1 ℝ) : m1.val ^ 2 = (m1 * m1).val := by rw [sq] rfl @@ -229,12 +243,13 @@ lemma scaleUnit_val {d : Dimension LTMCTDimensionBase} (M : Type) [MulAction ℝ -/ -noncomputable instance {B : Type} (d1 d2 : Dimension B) : +noncomputable instance {B : Type} [DimensionBasis B] (d1 d2 : Dimension B) : HDiv (WithDim d1 ℝ) (WithDim d2 ℝ) (WithDim (d1 * d2⁻¹) ℝ) where hDiv m1 m2 := ⟨m1.val / m2.val⟩ @[simp] -lemma val_div_val {B : Type} {d1 d2 : Dimension B} (m1 : WithDim d1 ℝ) (m2 : WithDim d2 ℝ) : +lemma val_div_val {B : Type} [DimensionBasis B] {d1 d2 : Dimension B} + (m1 : WithDim d1 ℝ) (m2 : WithDim d2 ℝ) : (m1.val / m2.val) = (m1 / m2).val := rfl @[simp] @@ -267,11 +282,11 @@ lemma scaleUnit_dim_eq_zero {d : Dimension LTMCTDimensionBase} (m : WithDim d set_option linter.unusedVariables false in /-- The casting from `WithDim d M` to `WithDim d2 M` when `d = d2`. -/ @[nolint unusedArguments] -def cast {B : Type} {d d2 : Dimension B} {M : Type} (m : WithDim d M) +def cast {B : Type} [DimensionBasis B] {d d2 : Dimension B} {M : Type} (m : WithDim d M) (h : d = d2 := by ext <;> {simp; try ring}) : WithDim d2 M := ⟨m.val⟩ @[simp] -lemma cast_refl {B : Type} {d : Dimension B} {M : Type} (m : WithDim d M) : +lemma cast_refl {B : Type} [DimensionBasis B] {d : Dimension B} {M : Type} (m : WithDim d M) : cast m rfl = m := rfl @[simp] From 0e2dda9a90a61fd32719fe20a121f9c867d2b7f1 Mon Sep 17 00:00:00 2001 From: Robby Sneiderman Date: Thu, 27 Aug 2026 04:47:24 -0500 Subject: [PATCH 16/20] feat(tensors): prove the Levi-Civita contraction identities (#1565) * feat(tensors): prove the Levi-Civita contraction identities * refactor(tensors): relocate reindexing lemmas * refactor(tensors): organize Levi-Civita contraction APIs Move reusable contraction, metric, unit, Minkowski, and Kronecker component lemmas into their owning modules. Keep the finite-index proof scaffolding private and the headline identities in LeviCivita/Contractions. * refactor(tensors): streamline Levi-Civita contractions * refactor(tensors): simplify contraction component routing --- Physlib.lean | 2 + Physlib/Mathematics/KroneckerDelta/Basic.lean | 11 + .../KroneckerDelta/Contraction.lean | 21 +- Physlib/Relativity/MinkowskiMatrix.lean | 26 ++ Physlib/Relativity/Tensors/API-map.yaml | 16 +- .../Tensors/ComponentIdx/Contraction.lean | 11 +- .../Tensors/Contraction/CrossToSlot.lean | 17 +- .../Relativity/Tensors/LeviCivita/Basic.lean | 17 -- .../Tensors/LeviCivita/Contractions.lean | 254 +++++++++++++++++- Physlib/Relativity/Tensors/MetricTensor.lean | 7 + .../RealTensor/Contraction/CrossToEnd.lean | 96 +++++++ .../Tensors/RealTensor/Metrics/Basic.lean | 75 +++++- .../Tensors/RealTensor/Units/Basic.lean | 74 +++++ Physlib/Relativity/Tensors/Reindexing.lean | 26 ++ Physlib/Relativity/Tensors/UnitTensor.lean | 8 + 15 files changed, 617 insertions(+), 44 deletions(-) create mode 100644 Physlib/Relativity/Tensors/RealTensor/Contraction/CrossToEnd.lean create mode 100644 Physlib/Relativity/Tensors/RealTensor/Units/Basic.lean diff --git a/Physlib.lean b/Physlib.lean index 0fc9591f14..f78b9df55c 100644 --- a/Physlib.lean +++ b/Physlib.lean @@ -452,11 +452,13 @@ public import Physlib.Relativity.Tensors.RealTensor.Basic public import Physlib.Relativity.Tensors.RealTensor.CoVector.Basic public import Physlib.Relativity.Tensors.RealTensor.CoVector.Representation public import Physlib.Relativity.Tensors.RealTensor.CoVector.Tensorial +public import Physlib.Relativity.Tensors.RealTensor.Contraction.CrossToEnd public import Physlib.Relativity.Tensors.RealTensor.Matrix.Pre public import Physlib.Relativity.Tensors.RealTensor.Metrics.Basic public import Physlib.Relativity.Tensors.RealTensor.Metrics.Pre public import Physlib.Relativity.Tensors.RealTensor.Representation.Contraction public import Physlib.Relativity.Tensors.RealTensor.ToComplex +public import Physlib.Relativity.Tensors.RealTensor.Units.Basic public import Physlib.Relativity.Tensors.RealTensor.Units.Pre public import Physlib.Relativity.Tensors.RealTensor.Vector.Basic public import Physlib.Relativity.Tensors.RealTensor.Vector.Causality.Basic diff --git a/Physlib/Mathematics/KroneckerDelta/Basic.lean b/Physlib/Mathematics/KroneckerDelta/Basic.lean index 026cf6d9d6..846e490428 100644 --- a/Physlib/Mathematics/KroneckerDelta/Basic.lean +++ b/Physlib/Mathematics/KroneckerDelta/Basic.lean @@ -201,6 +201,17 @@ lemma generalizedKroneckerDelta_swap {α ι : Type} [DecidableEq α] [DecidableE rw [Matrix.det_permute, Equiv.Perm.sign_swap hij] simp +/-- Simultaneously reindexing the upper and lower slots of a generalized Kronecker delta by the +same permutation leaves it unchanged. -/ +@[simp] +lemma generalizedKroneckerDelta_comp_perm {α ι : Type} [DecidableEq α] [DecidableEq ι] + [Fintype ι] (μ ν : ι → α) (e : Equiv.Perm ι) : + generalizedKroneckerDelta (μ ∘ e) (ν ∘ e) = generalizedKroneckerDelta μ ν := by + show (Matrix.submatrix + (Matrix.of fun i j => ((kroneckerDelta (μ i) (ν j) : ℕ) : ℤ)) e e).det = + (Matrix.of fun i j => ((kroneckerDelta (μ i) (ν j) : ℕ) : ℤ)).det + exact Matrix.det_submatrix_equiv_self e _ + end Generalized end KroneckerDelta diff --git a/Physlib/Mathematics/KroneckerDelta/Contraction.lean b/Physlib/Mathematics/KroneckerDelta/Contraction.lean index 881fc0ef09..9a9383da5e 100644 --- a/Physlib/Mathematics/KroneckerDelta/Contraction.lean +++ b/Physlib/Mathematics/KroneckerDelta/Contraction.lean @@ -36,8 +36,8 @@ matrix determinant lemma when `det A` is a unit and Kronecker-delta matrices are - `generalizedKroneckerDelta_sum_snoc` : summing over one shared index lowers the rank by one. - `sum_generalizedKroneckerDelta_mul_self`, `sum_generalizedKroneckerDelta_mul_cons`, - `sum_generalizedKroneckerDelta_mul_cons₂` : the fully-, singly-, and doubly-free symbol-level - contractions over `Fin 4`. + `sum_generalizedKroneckerDelta_mul_snoc`, `sum_generalizedKroneckerDelta_mul_cons₂` : the + fully-, singly-, and doubly-free symbol-level contractions over `Fin 4`. ## iii. Table of contents @@ -309,6 +309,23 @@ lemma sum_generalizedKroneckerDelta_mul_cons (σ τ : Fin 4) : sum_generalizedKroneckerDelta_cons σ τ 3] norm_num [Finset.prod_range_succ] +/-- Symbol-level triple contraction with the free index in the last slot. -/ +lemma sum_generalizedKroneckerDelta_mul_snoc (σ τ : Fin 4) : + ∑ h : Fin 3 → Fin 4, + generalizedKroneckerDelta (Fin.snoc h σ) id * generalizedKroneckerDelta (Fin.snoc h τ) id = + 6 * ((kroneckerDelta σ τ : ℕ) : ℤ) := by + rw [Finset.sum_congr rfl fun h _ => generalizedKroneckerDelta_mul (Fin.snoc h σ) (Fin.snoc h τ)] + have hrotate (h : Fin 3 → Fin 4) : + generalizedKroneckerDelta (Fin.snoc h σ) (Fin.snoc h τ) = + generalizedKroneckerDelta (Fin.cons σ h) (Fin.cons τ h) := by + rw [Fin.snoc_eq_cons_rotate, Fin.snoc_eq_cons_rotate] + change generalizedKroneckerDelta ((Fin.cons σ h) ∘ finRotate (3 + 1)) + ((Fin.cons τ h) ∘ finRotate (3 + 1)) = + generalizedKroneckerDelta (Fin.cons σ h) (Fin.cons τ h) + exact generalizedKroneckerDelta_comp_perm _ _ _ + rw [Finset.sum_congr rfl fun h _ => hrotate h, sum_generalizedKroneckerDelta_cons σ τ 3] + norm_num [Finset.prod_range_succ] + /-- Symbol-level double contraction, two free pairs. -/ lemma sum_generalizedKroneckerDelta_mul_cons₂ (ρ σ τ ω : Fin 4) : ∑ h : Fin 2 → Fin 4, diff --git a/Physlib/Relativity/MinkowskiMatrix.lean b/Physlib/Relativity/MinkowskiMatrix.lean index d06f5350ec..ff851798fd 100644 --- a/Physlib/Relativity/MinkowskiMatrix.lean +++ b/Physlib/Relativity/MinkowskiMatrix.lean @@ -129,6 +129,13 @@ lemma off_diag_zero {μ ν : Fin 1 ⊕ Fin d} (h : μ ≠ ν) : η μ ν = 0 := lemma η_diag_ne_zero {μ : Fin 1 ⊕ Fin d} : η μ μ ≠ 0 := by aesop (add safe forward as_diagonal) +/-- Right multiplication of a row vector by the Minkowski matrix multiplies each component by +the corresponding diagonal sign. -/ +lemma vecMul_apply (v : (Fin 1 ⊕ Fin d) → ℝ) (μ : Fin 1 ⊕ Fin d) : + (v ᵥ* minkowskiMatrix) μ = v μ * minkowskiMatrix μ μ := by + rw [as_diagonal, Matrix.vecMul_diagonal] + simp + /-! ### A.4. Squaring the Minkowski matrix @@ -180,6 +187,25 @@ We show the determinant of the Minkowski matrix is equal to `(-1)^d` where lemma det_eq_neg_one_pow_d : (@minkowskiMatrix d).det = (- 1) ^ d := by simp [as_diagonal] +/-- The product of all diagonal entries of the Minkowski matrix is `(-1) ^ d`. -/ +lemma prod_diagonal : ∏ μ : Fin 1 ⊕ Fin d, minkowskiMatrix μ μ = (-1) ^ d := by + rw [as_diagonal] + simp only [Matrix.diagonal_apply_eq] + rw [Fintype.prod_sum_type] + simp + +/-- Reindexing all diagonal entries injectively does not change their product. -/ +lemma prod_diagonal_comp_of_injective {v : Fin (d + 1) → Fin 1 ⊕ Fin d} + (hv : Function.Injective v) : + ∏ i, minkowskiMatrix (v i) (v i) = (-1) ^ d := by + have hcard : Fintype.card (Fin (d + 1)) = Fintype.card (Fin 1 ⊕ Fin d) := by + simp [Nat.add_comm] + have hbij : Function.Bijective v := + (Fintype.bijective_iff_injective_and_card v).mpr ⟨hv, hcard⟩ + let e : Fin (d + 1) ≃ Fin 1 ⊕ Fin d := Equiv.ofBijective v hbij + change ∏ i, minkowskiMatrix (e i) (e i) = (-1) ^ d + exact (Equiv.prod_comp e (fun μ => minkowskiMatrix μ μ)).trans prod_diagonal + /-! ### A.7. Injective properties of multiplying diagonal components diff --git a/Physlib/Relativity/Tensors/API-map.yaml b/Physlib/Relativity/Tensors/API-map.yaml index 1fbf1d3adc..41a6a9eb64 100644 --- a/Physlib/Relativity/Tensors/API-map.yaml +++ b/Physlib/Relativity/Tensors/API-map.yaml @@ -41,7 +41,7 @@ Requirements: - description: "The API contains the type `Tensor` of tensors with a given list of index colors, the type `Pure` of pure tensors, the type `ComponentIdx` of component labels, the passage between a tensor and its components, the component basis, and the identification of rank-zero tensors with the base field." done: true location: | - Physlib/Relativity/Tensors/Basic.lean (Tensor, Pure, Pure.toTensor, Pure.component, Pure.basisVector, Tensor.componentMap, ofComponents, basis, basis_repr_pure, toField); Physlib/Relativity/Tensors/ComponentIdx/Basic.lean (ComponentIdx, ComponentIdx.congr_right, ComponentIdx.cast); Physlib/Relativity/Tensors/ComponentIdx/Single.lean (ComponentIdx.single); Physlib/Relativity/Tensors/ComponentIdx/Product.lean (ComponentIdx.prod); Physlib/Relativity/Tensors/ComponentIdx/Contraction.lean (ComponentIdx.dropPair, ComponentIdx.DropPairSection, ComponentIdx.DropPairSection.ofFinEquiv) + Physlib/Relativity/Tensors/Basic.lean (Tensor, Pure, Pure.toTensor, Pure.component, Pure.basisVector, Tensor.componentMap, ofComponents, basis, basis_repr_pure, toField); Physlib/Relativity/Tensors/ComponentIdx/Basic.lean (ComponentIdx, ComponentIdx.congr_right, ComponentIdx.cast); Physlib/Relativity/Tensors/ComponentIdx/Single.lean (ComponentIdx.single); Physlib/Relativity/Tensors/ComponentIdx/Product.lean (ComponentIdx.prod); Physlib/Relativity/Tensors/ComponentIdx/Contraction.lean (ComponentIdx.dropPair, ComponentIdx.DropPairSection, ComponentIdx.DropPairSection.ofFinEquiv, ComponentIdx.DropPairSection.ofFinEquiv_dropPair) - description: "The API contains the action of the symmetry group on pure tensors and on tensors, the permutation of indices along a color-preserving map, and the notion `IsReindexing` of such a map together with its closure under inverse, composition and the index maps used by products, evaluation and contraction." done: true @@ -55,12 +55,12 @@ Requirements: - description: "The API contains the contraction of a pair of indices of dual color, on pure tensors and on tensors, with its equivariance, its components in the basis, its interaction with permutations and products, and the slot-addressed contraction of one tensor against another in both the result-to-end and result-to-slot conventions." done: true location: | - Physlib/Relativity/Tensors/Contraction/SuccSuccAbove.lean (Fin.succSuccAbove, Fin.predPredAbove, Fin.funPredPredAbove); Physlib/Relativity/Tensors/Contraction/Pure.lean (Pure.dropPair, Pure.contrPCoeff, Pure.contrP, Pure.contrPMultilinear); Physlib/Relativity/Tensors/Contraction/Basic.lean (contrT, contrT_pure, contrT_equivariant, contrT_permT, contrT_symm, contrT_comm); Physlib/Relativity/Tensors/Contraction/Basis.lean (contrT_basis_repr_apply, contrT_basis_repr_apply_eq_sum_fin, contrT_basis); Physlib/Relativity/Tensors/Contraction/Products.lean (prodT_contrT_snd, contrT_prodT_snd, prodT_contrT_fst); Physlib/Relativity/Tensors/Contraction/CrossToEnd.lean (crossToEnd, crossToEnd_two, crossToEnd_equivariant, crossToEnd_assoc_rankTwo, crossToEnd_permT_left, crossToEnd_permT_right); Physlib/Relativity/Tensors/Contraction/CrossToSlot.lean (crossToSlot, crossToSlot_eq_crossToEnd, crossToSlotInv, crossToSlot_equivariant) + Physlib/Relativity/Tensors/Contraction/SuccSuccAbove.lean (Fin.succSuccAbove, Fin.predPredAbove, Fin.funPredPredAbove); Physlib/Relativity/Tensors/Contraction/Pure.lean (Pure.dropPair, Pure.contrPCoeff, Pure.contrP, Pure.contrPMultilinear); Physlib/Relativity/Tensors/Contraction/Basic.lean (contrT, contrT_pure, contrT_equivariant, contrT_permT, contrT_symm, contrT_comm); Physlib/Relativity/Tensors/Contraction/Basis.lean (contrT_basis_repr_apply, contrT_basis_repr_apply_eq_sum_fin, contrT_basis); Physlib/Relativity/Tensors/Contraction/Products.lean (prodT_contrT_snd, contrT_prodT_snd, prodT_contrT_fst); Physlib/Relativity/Tensors/Contraction/CrossToEnd.lean (crossToEnd, crossToEnd_two, crossToEnd_equivariant, crossToEnd_assoc_rankTwo, crossToEnd_permT_left, crossToEnd_permT_right); Physlib/Relativity/Tensors/Contraction/CrossToSlot.lean (crossToSlot, crossToSlot_eq_crossToEnd, crossToSlot_basis_repr_apply, crossToSlotInv, crossToSlot_equivariant); Physlib/Relativity/Tensors/RealTensor/Contraction/CrossToEnd.lean (crossToEnd_basis_repr_apply_eq_fin) - description: "The API contains the unit tensor and the metric tensor of a color, their invariance under the group action, the collapse of a metric contracted against the metric at the dual color, the unit tensor as an identity for slot contraction, and the raising and lowering of a named index as a linear equivalence." done: true location: | - Physlib/Relativity/Tensors/UnitTensor.lean (unitTensor, unitTensor_eq_permT_dual, contrT_single_unitTensor, unitTensor_invariant); Physlib/Relativity/Tensors/MetricTensor.lean (metricTensor, metricTensor_invariant, contrT_metricTensor_metricTensor, contrT_metricTensor_metricTensor_eq_dual_unit); Physlib/Relativity/Tensors/Contraction/UnitTensorContraction.lean (crossToEnd_unitTensor, crossToEnd_round_trip_of_unit_slot, crossToSlot_raise_lower_round_trip, crossToSlotEquiv); Physlib/Relativity/Tensors/Dual.lean (toDualMapAtIndex, fromDualMapAtIndex, toDualMapAtIndex_toDualMapAtIndex, toDualMapAtIndex_equivariant, toDualAtIndex) + Physlib/Relativity/Tensors/UnitTensor.lean (unitTensor, unitTensor_basis_repr, unitTensor_eq_permT_dual, contrT_single_unitTensor, unitTensor_invariant); Physlib/Relativity/Tensors/MetricTensor.lean (metricTensor, metricTensor_basis_repr, metricTensor_invariant, contrT_metricTensor_metricTensor, contrT_metricTensor_metricTensor_eq_dual_unit); Physlib/Relativity/Tensors/Contraction/UnitTensorContraction.lean (crossToEnd_unitTensor, crossToEnd_round_trip_of_unit_slot, crossToSlot_raise_lower_round_trip, crossToSlotEquiv); Physlib/Relativity/Tensors/Dual.lean (toDualMapAtIndex, fromDualMapAtIndex, toDualMapAtIndex_toDualMapAtIndex, toDualMapAtIndex_equivariant, toDualAtIndex) - description: "The API contains the evaluation of one index of a tensor at a fixed basis label, its components in the basis, its commutation with permutations, other evaluations, contractions and products, and the reconstruction of a tensor as the sum over basis labels of the evaluations of its last index, each tensored with the matching rank-one basis tensor and permuted back into the last slot." done: true @@ -83,7 +83,7 @@ Requirements: - description: "The API contains the tensor species `realLorentzTensor d` of real Lorentz tensors, with colors `up` and `down`, built from the contravariant and covariant modules, their representations of the Lorentz group, their bases, and the contraction, metric and unit maps; the metric tensors of the species are computed in the standard basis, and the pairwise tensor products of the contravariant and covariant modules are identified with square matrices." done: true location: | - Physlib/Relativity/Tensors/RealTensor/Basic.lean (realLorentzTensor, realLorentzTensor.Color, τ_up_eq_down, τ_down_eq_up, contrPCoeff_basis, contrT_eq_sum_evalT, contrT_toField); Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Modules.lean (ContrMod, CoMod, AddCommGroup (ContrMod d), Module ℝ (ContrMod d), AddCommGroup (CoMod d), Module ℝ (CoMod d), ContrMod.rep, CoMod.rep, ContrMod.stdBasis, CoMod.stdBasis); Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Basic.lean (contrBasis, coBasis, contrIsoCo); Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Contraction.lean (contrCoContract, coContrContract, contrContrContractField); Physlib/Relativity/Tensors/RealTensor/Metrics/Pre.lean (preContrMetric, preCoMetric); Physlib/Relativity/Tensors/RealTensor/Units/Pre.lean (preContrCoUnit, preCoContrUnit); Physlib/Relativity/Tensors/RealTensor/Metrics/Basic.lean (coMetric, contrMetric, actionT_coMetric, actionT_contrMetric, coMetric_repr_apply_eq_minkowskiMatrix, contrMetric_repr_apply_eq_minkowskiMatrix); Physlib/Relativity/Tensors/RealTensor/Matrix/Pre.lean (contrContrToMatrixRe, coCoToMatrixRe, contrCoToMatrixRe, coContrToMatrixRe) + Physlib/Relativity/Tensors/RealTensor/Basic.lean (realLorentzTensor, realLorentzTensor.Color, τ_up_eq_down, τ_down_eq_up, contrPCoeff_basis, contrT_eq_sum_evalT, contrT_toField); Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Modules.lean (ContrMod, CoMod, AddCommGroup (ContrMod d), Module ℝ (ContrMod d), AddCommGroup (CoMod d), Module ℝ (CoMod d), ContrMod.rep, CoMod.rep, ContrMod.stdBasis, CoMod.stdBasis); Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Basic.lean (contrBasis, coBasis, contrIsoCo); Physlib/Relativity/Tensors/RealTensor/Vector/Pre/Contraction.lean (contrCoContract, coContrContract, contrContrContractField); Physlib/Relativity/Tensors/RealTensor/Metrics/Pre.lean (preContrMetric, preCoMetric); Physlib/Relativity/Tensors/RealTensor/Units/Pre.lean (preContrCoUnit, preCoContrUnit); Physlib/Relativity/Tensors/RealTensor/Metrics/Basic.lean (coMetric, contrMetric, actionT_coMetric, actionT_contrMetric, coMetric_repr_apply_eq_minkowskiMatrix, contrMetric_repr_apply_eq_minkowskiMatrix, metricTensor_repr_apply_eq_minkowskiMatrix, toDualMapAtIndex_basis_repr_apply, toDualMapAtIndex_basis_repr_apply_eq_mul); Physlib/Relativity/Tensors/RealTensor/Units/Basic.lean (unitTensor_repr_apply); Physlib/Relativity/Tensors/RealTensor/Matrix/Pre.lean (contrContrToMatrixRe, coCoToMatrixRe, contrCoToMatrixRe, coContrToMatrixRe) - description: "The API contains Lorentz vectors and covectors as functions on `Fin 1 ⊕ Fin d`, with their module, norm and inner-product structures, their charted-space structures, their standard bases, their representations of the Lorentz group, their tensorial instances and the invariant contraction between them, together with the coordinate maps and the time and spatial parts of a vector, and the model with corners under which vectors are treated as a manifold." done: true @@ -98,7 +98,7 @@ Requirements: - description: "The API contains the rank-four Levi-Civita tensor as a real Lorentz tensor in three spatial dimensions, its components in the standard basis as a Levi-Civita symbol, its antisymmetry under each adjacent transposition of indices, and the epsilon-epsilon contraction identities at the level of the Euclidean Levi-Civita symbol, summed over all four index slots, over the last three, and over the last two." done: true location: | - Physlib/Relativity/Tensors/LeviCivita/Basic.lean (leviCivita, notation ε4, euclidLeviCivita, leviCivita_basis_repr_apply, leviCivita_basis_repr_eq_leviCivitaSymbol, leviCivita_antisymm, leviCivita_antisymm_mid, leviCivita_antisymm_last); Physlib/Relativity/Tensors/LeviCivita/Contractions.lean (euclidLeviCivita_symbol_contract_zero, euclidLeviCivita_symbol_contract_one, euclidLeviCivita_symbol_contract_two) + Physlib/Relativity/Tensors/LeviCivita/Basic.lean (leviCivita, notation ε4, euclidLeviCivita, leviCivita_basis_repr_apply, leviCivita_basis_repr_eq_leviCivitaSymbol, leviCivita_antisymm, leviCivita_antisymm_mid, leviCivita_antisymm_last); Physlib/Relativity/Tensors/LeviCivita/Contractions.lean (euclidLeviCivita_symbol_contract_zero, euclidLeviCivita_symbol_contract_one, euclidLeviCivita_symbol_contract_one_last, euclidLeviCivita_symbol_contract_two, leviCivita_lowered_basis_repr_apply, leviCivita_basis_contract_self, leviCivita_basis_contract_three, leviCivita_contract_three_basis_repr_apply, leviCivita_contract_self_eq_sum, leviCivita_contract_self, leviCivita_contract_three) - description: "The API contains the tensor species `complexLorentzTensor` of complex Lorentz tensors, with the left- and right-handed Weyl colors, their duals and the two Lorentz vector colors over SL(2, ℂ), together with the metric and unit tensors of each color in several equivalent forms and the identification of pairwise products of complex Lorentz vectors with matrices." done: true @@ -121,9 +121,9 @@ Requirements: done: false location: N/A - - description: "The tensor-level epsilon-epsilon identities for the Levi-Civita tensor: contracting it with itself over all four index pairs gives the field element `-24`, and contracting it with itself over the first three gives `-6` times the unit tensor of color `down`. Stated as `leviCivita_contract_self` and `leviCivita_contract_three`, both carrying the repository's marker for a result that is not yet proved." - done: false - location: N/A + - description: "The tensor-level epsilon-epsilon identities for the Levi-Civita tensor: contracting it with itself over all four index pairs gives the field element `-24`, and contracting it with itself over the first three gives `-6` times the unit tensor of color `down`." + done: true + location: "Physlib/Relativity/Tensors/LeviCivita/Contractions.lean (leviCivita_lowered_basis_repr_apply, leviCivita_contract_self, leviCivita_contract_three); Physlib/Relativity/Tensors/RealTensor/Metrics/Basic.lean (toDualMapAtIndex_basis_repr_apply, toDualMapAtIndex_basis_repr_apply_eq_mul); Physlib/Relativity/Tensors/RealTensor/Units/Basic.lean (unitTensor_repr_apply)" - description: "The API shall contain a Euclidean Levi-Civita tensor to carry the epsilon-epsilon contraction identities, which at present are stated for the Euclidean Levi-Civita symbol `euclidLeviCivita` alone." done: false diff --git a/Physlib/Relativity/Tensors/ComponentIdx/Contraction.lean b/Physlib/Relativity/Tensors/ComponentIdx/Contraction.lean index b09c2f539b..61e22f9155 100644 --- a/Physlib/Relativity/Tensors/ComponentIdx/Contraction.lean +++ b/Physlib/Relativity/Tensors/ComponentIdx/Contraction.lean @@ -1,7 +1,7 @@ /- Copyright (c) 2025 Joseph Tooby-Smith. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Joseph Tooby-Smith +Authors: Robert Sneiderman, Joseph Tooby-Smith -/ module @@ -180,6 +180,15 @@ lemma ofFinEquiv_apply_snd {n : ℕ} {c : Fin (n + 1 + 1) → C} (ofFinEquiv (S := S) hij b x).1 j = x.2 := by simp [ofFinEquiv] +/-- Restoring the two entries dropped from a component index recovers that component index. -/ +@[simp] +lemma ofFinEquiv_dropPair {n : ℕ} {c : Fin (n + 1 + 1) → C} + {i j : Fin (n + 1 + 1)} (hij : i ≠ j) (r : ComponentIdx (S := S) c) : + (ofFinEquiv (S := S) hij (r.dropPair i j) (r i, r j)).1 = r := by + exact congrArg Subtype.val <| + (ofFinEquiv (S := S) hij (r.dropPair i j)).apply_symm_apply + ⟨r, mem_self_of_dropPair r⟩ + end DropPairSection end ComponentIdx diff --git a/Physlib/Relativity/Tensors/Contraction/CrossToSlot.lean b/Physlib/Relativity/Tensors/Contraction/CrossToSlot.lean index c91c257638..2031873ff5 100644 --- a/Physlib/Relativity/Tensors/Contraction/CrossToSlot.lean +++ b/Physlib/Relativity/Tensors/Contraction/CrossToSlot.lean @@ -1,7 +1,7 @@ /- Copyright (c) 2026 Andrea Pari. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Andrea Pari +Authors: Andrea Pari, Robert Sneiderman -/ module @@ -27,6 +27,8 @@ operation live with the unit-tensor collapse theory in - `TensorSpecies.Tensor.crossToSlot` : contract slot `i` against slot `j` of a rank-2 tensor and rotate the survivor back into position `i`; raising and lowering a named index. - `TensorSpecies.Tensor.crossToSlot_eq_crossToEnd` : the bridge to the result-to-end convention. +- `TensorSpecies.Tensor.crossToSlot_basis_repr_apply` : the component bridge from result-to-slot + to result-to-end contraction. - `TensorSpecies.Tensor.crossToSlotInv` : the returning half of a round trip, the contraction against the second factor with the round trip's color cast absorbed. - `TensorSpecies.Tensor.crossToSlot_permT_right_id` : an identity reindexing of the rank-2 tensor @@ -100,6 +102,19 @@ lemma crossToSlot_eq_crossToEnd {nA : ℕ} {c : Fin (nA + 1) → C} {cM : Fin 2 permT ⇑(Fin.cycleIcc i (Fin.last nA)).symm (IsReindexing.crossToSlot_cycle i j) (crossToEnd i j hc t M) := rfl +/-- A component of `crossToSlot` is the corresponding component of `crossToEnd`, with the +surviving indices rotated back into the contracted slot. -/ +lemma crossToSlot_basis_repr_apply {nA : ℕ} {c : Fin (nA + 1) → C} {cM : Fin 2 → C} + (i : Fin (nA + 1)) (j : Fin 2) (hc : S.τ (c i) = cM j) (M : Tensor S cM) + (t : Tensor S c) + (φ : ComponentIdx (S := S) (Function.update c i (cM (j.succAbove 0)))) : + (basis _).repr (crossToSlot i j hc M t) φ = + (basis _).repr (crossToEnd i j hc t M) (fun m => + basisIdxCongr ((IsReindexing.crossToSlot_cycle i j).inv_perserve_color m) + (φ ((IsReindexing.crossToSlot_cycle i j).inv + ⇑(Fin.cycleIcc i (Fin.last nA)).symm m))) := by + rw [crossToSlot_eq_crossToEnd, permT_basis_repr_symm_apply] + /-- Contract slot `i` of a tensor whose color there is `d` against `M'`, then absorb the color cast the two `Function.update`s generate, landing back on `c`. This is the returning half of a raise-then-lower round trip; absorbing the cast here is what keeps the round trip cast-free at diff --git a/Physlib/Relativity/Tensors/LeviCivita/Basic.lean b/Physlib/Relativity/Tensors/LeviCivita/Basic.lean index 3df63bf374..cb2e89fad8 100644 --- a/Physlib/Relativity/Tensors/LeviCivita/Basic.lean +++ b/Physlib/Relativity/Tensors/LeviCivita/Basic.lean @@ -6,8 +6,6 @@ Authors: Robert Sneiderman module public import Physlib.Relativity.Tensors.RealTensor.Basic -public import Physlib.Relativity.Tensors.UnitTensor -public import Physlib.Meta.Sorry public import Physlib.Relativity.Tensors.OfInt public import Physlib.Mathematics.LeviCivita.Basic /-! @@ -174,19 +172,4 @@ lemma leviCivita_antisymm_last : {ε4 | μ ν ρ σ = - (ε4 | μ ν σ ρ)}ᵀ funext i fin_cases i <;> rfl -open TensorSpecies Tensor - -@[sorryful] -lemma leviCivita_contract_three : {ε4 | μ ν ρ σ ⊗ ε4 | τ(μ) τ(ν) τ(ρ) τ(τ) = - (-6) • unitTensor (S := realLorentzTensor) Color.down | σ τ }ᵀ := by - sorry - --- `checkType` linter: under the v4.32.0 toolchain, whnf on this tensor-notation --- statement exceeds the linter's 200k-heartbeat budget (it did not on v4.31.0). --- Statement unchanged; see the v4.32.0 bump commit message. -@[sorryful, nolint checkType] -lemma leviCivita_contract_self : - {ε4 | μ ν ρ σ ⊗ ε4 | τ(μ) τ(ν) τ(ρ) τ(σ)}ᵀ.toField = - 24 := by - sorry - end realLorentzTensor diff --git a/Physlib/Relativity/Tensors/LeviCivita/Contractions.lean b/Physlib/Relativity/Tensors/LeviCivita/Contractions.lean index f525f6a2f1..771c2c24b1 100644 --- a/Physlib/Relativity/Tensors/LeviCivita/Contractions.lean +++ b/Physlib/Relativity/Tensors/LeviCivita/Contractions.lean @@ -7,10 +7,11 @@ module public import Physlib.Relativity.Tensors.LeviCivita.Basic public import Physlib.Mathematics.KroneckerDelta.Contraction -public import Physlib.Meta.TODO.Basic +public import Physlib.Relativity.Tensors.RealTensor.Metrics.Basic +public import Physlib.Relativity.Tensors.RealTensor.Units.Basic /-! -# Euclidean contraction identities for the Levi-Civita tensor +# Contraction identities for the Levi-Civita tensor ## i. Overview @@ -19,23 +20,38 @@ tensor `leviCivita` (notation `ε4`) in `d = 4`, stated in terms of the standard components of `ε4` itself (`realLorentzTensor.leviCivita_basis_repr_apply`). The underlying facts about the `generalizedKroneckerDelta` alone, with no -tensor content — lives in `Physlib.Mathematics.KroneckerDelta.Contraction`, next to the +tensor content, live in `Physlib.Mathematics.KroneckerDelta.Contraction`, next to the definition of `generalizedKroneckerDelta`. Here we specialise those facts to the components of `ε4`, where `(ε4)_b = (Tensor.basis _).repr ε4 b` is the standard-basis component of `ε4`, an integer Levi-Civita symbol carried to the reals, and the sums run over the remaining (uncontracted) component slots. +It also proves the Lorentzian tensor identities obtained by lowering all four indices of one +factor: the complete contraction is `-24`, while contracting three index pairs gives `-6` times +the unit tensor. + +The Lorentzian proofs proceed through reusable component statements: lowering all four indices +contributes the orientation sign, tensor contractions become finite sums of matching components, +and the Euclidean contraction theorems evaluate those sums. + ## ii. Key results -- `leviCivita_symbol_contract_zero` : `∑_b (ε4)_b · (ε4)_b = 24` (full Euclidean contraction). -- `leviCivita_symbol_contract_one` : `∑_h (ε4)_{a,h} · (ε4)_{b,h} = 6 · δ[a,b]`. -- `leviCivita_symbol_contract_two` : +- `euclidLeviCivita_symbol_contract_zero` : full Euclidean contraction equals `24`. +- `euclidLeviCivita_symbol_contract_one` : the triple Euclidean contraction equals `6 · δ[a,b]`. +- `euclidLeviCivita_symbol_contract_two` : `∑_h (ε4)_{r,s,h} · (ε4)_{t,w,h} = 2 · (δ[r,t]·δ[s,w] - δ[r,w]·δ[s,t])`. +- `realLorentzTensor.leviCivita_lowered_basis_repr_apply` : lowering all four indices changes + every standard-basis component by the Lorentzian orientation sign `-1`. +- `realLorentzTensor.leviCivita_contract_three_basis_repr_apply` : the tensor triple contraction + is the sum of matching standard-basis components. +- `leviCivita_contract_self` : `ε^{μνρσ} ε_{μνρσ} = -24`. +- `leviCivita_contract_three` : `ε^{μνρσ} ε_{μνρτ} = -6 δ^σ_τ`. ## iii. Table of contents -- A. The combinatorial bridge lemma -- B. Euclidean epsilon-epsilon contraction identities +- A. Euclidean epsilon-epsilon contraction identities +- B. Lorentzian epsilon-epsilon contraction identities + - B.1. The epsilon-epsilon contraction identities ## iv. References @@ -45,7 +61,6 @@ integer Levi-Civita symbol carried to the reals, and the sums run over the remai open Matrix TensorSpecies Tensor KroneckerDelta - /-! ## A. Euclidean epsilon-epsilon contraction identities @@ -90,6 +105,18 @@ lemma euclidLeviCivita_symbol_contract_one (a b : Fin 4) : sum_generalizedKroneckerDelta_mul_cons] push_cast; ring +/-- **Triple Euclidean Levi-Civita contraction with the free index last.** This is the same +contraction as `euclidLeviCivita_symbol_contract_one`, in the slot order produced by the tensor +notation for `ε^{μνρσ} ε_{μνρτ}`. -/ +lemma euclidLeviCivita_symbol_contract_one_last (a b : Fin 4) : + ∑ h : Fin 3 → Fin 4, euclidLeviCivita (Fin.snoc h a) * euclidLeviCivita (Fin.snoc h b) + = 6 * ((kroneckerDelta a b : ℕ) : ℝ) := by + rw [Finset.sum_congr rfl fun h _ => ?_, euclidLeviCivita_symbol_contract_one a b] + simp only [euclidLeviCivita, ← Int.cast_mul, generalizedKroneckerDelta_mul] + rw [Fin.snoc_eq_cons_rotate, Fin.snoc_eq_cons_rotate] + exact congrArg (fun z : ℤ => (z : ℝ)) + (generalizedKroneckerDelta_comp_perm (Fin.cons a h) (Fin.cons b h) (finRotate (3 + 1))) + /-- **Double Euclidean Levi-Civita contraction** `∑_h (ε4)_{r,s,h} · (ε4)_{t,w,h} = 2 · (δ[r,t]·δ[s,w] - δ[r,w]·δ[s,t])` at the symbol level: contracting two of the four `Fin 4` component slots of `ε4` with the naive Kronecker pairing @@ -113,3 +140,212 @@ lemma euclidLeviCivita_symbol_contract_two (r s t w : Fin 4) : rw [Finset.sum_congr rfl fun h' _ => hcast h', ← Int.cast_sum, sum_generalizedKroneckerDelta_mul_cons₂] push_cast; ring + +/-! + +## B. Lorentzian epsilon-epsilon contraction identities + +-/ + +namespace realLorentzTensor + +open TensorSpecies Tensor +open ComponentIdx.DropPairSection + +/-- Lowering all four indices of the Levi-Civita tensor changes the sign of every standard-basis +component. This is the tensor-component form of the Lorentzian orientation factor +`det η = -1`. -/ +lemma leviCivita_lowered_basis_repr_apply + (b : ComponentIdx (S := realLorentzTensor 3) + ![Color.down, Color.down, Color.down, Color.down]) : + (Tensor.basis _).repr ({ε4 | τ(μ) τ(ν) τ(ρ) τ(σ)}ᵀ) b = + - (Tensor.basis _).repr ε4 b := by + simp only [toDualMapAtIndex_basis_repr_apply_eq_mul] + by_cases hb : Function.Injective b + · have hp := minkowskiMatrix.prod_diagonal_comp_of_injective hb + rw [Fin.prod_univ_four] at hp + norm_num at hp + linear_combination ((Tensor.basis _).repr ε4 b) * hp + · have hcomp : ¬ Function.Injective (fun i => finSumFinEquiv (b i)) := + fun h => hb (Function.Injective.of_comp h) + rw [leviCivita_basis_repr_eq_leviCivitaSymbol, + leviCivitaSymbol_eq_zero_of_not_injective hcomp] + norm_num + +/-- The sum of the squared standard-basis components of the contravariant Levi-Civita tensor is +`4! = 24`. -/ +lemma leviCivita_basis_contract_self : + ∑ b : ComponentIdx (S := realLorentzTensor 3) + ![Color.up, Color.up, Color.up, Color.up], + (Tensor.basis _).repr ε4 b * (Tensor.basis _).repr ε4 b = 24 := by + calc + _ = ∑ g : Fin 4 → Fin 4, euclidLeviCivita g * euclidLeviCivita g := + Fintype.sum_equiv (Equiv.arrowCongr (Equiv.refl (Fin 4)) + (finSumFinEquiv : (Fin 1 ⊕ Fin 3) ≃ Fin 4)) _ _ fun b => by + rw [leviCivita_basis_repr_apply] + rfl + _ = 24 := euclidLeviCivita_symbol_contract_zero + +/-- Contracting the first three standard-basis components of two contravariant Levi-Civita tensors +gives `3! = 6` times the Kronecker delta on the remaining components. -/ +lemma leviCivita_basis_contract_three (a b : Fin 1 ⊕ Fin 3) : + ∑ h : Fin 3 → Fin 1 ⊕ Fin 3, + (Tensor.basis _).repr ε4 (Fin.snoc h a) * + (Tensor.basis _).repr ε4 (Fin.snoc h b) = + 6 * (if a = b then 1 else 0) := by + simp only [leviCivita_basis_repr_apply] + have hs (y : Fin 1 ⊕ Fin 3) (h : Fin 3 → Fin 1 ⊕ Fin 3) : + (fun i => finSumFinEquiv ((Fin.snoc h y : Fin 4 → Fin 1 ⊕ Fin 3) i)) = + Fin.snoc (fun i => finSumFinEquiv (h i)) (finSumFinEquiv y) := by + funext i + fin_cases i <;> rfl + rw [Finset.sum_congr rfl fun h _ => by rw [hs a h, hs b h]] + calc + _ = ∑ g : Fin 3 → Fin 4, + (generalizedKroneckerDelta (Fin.snoc g (finSumFinEquiv a)) id : ℝ) * + (generalizedKroneckerDelta (Fin.snoc g (finSumFinEquiv b)) id : ℝ) := + Fintype.sum_equiv (Equiv.arrowCongr (Equiv.refl (Fin 3)) + (finSumFinEquiv : (Fin 1 ⊕ Fin 3) ≃ Fin 4)) _ _ fun _ => rfl + _ = 6 * ((kroneckerDelta (finSumFinEquiv a) (finSumFinEquiv b) : ℕ) : ℝ) := + euclidLeviCivita_symbol_contract_one_last _ _ + _ = 6 * (if a = b then 1 else 0) := by + by_cases hab : a = b + · subst hab + simp [KroneckerDelta.eq_one_of_same] + · rw [if_neg hab, + KroneckerDelta.eq_zero_of_ne (fun h => hab (finSumFinEquiv.injective h))] + norm_num + +/-- The standard-basis component formula for the tensor contraction +`ε^{μνρσ} ε_{μνρτ}`. -/ +lemma leviCivita_contract_three_basis_repr_apply + (b : ComponentIdx (S := realLorentzTensor 3) ![Color.up, Color.down]) : + (Tensor.basis _).repr + {ε4 | μ ν ρ σ ⊗ ε4 | τ(μ) τ(ν) τ(ρ) τ(τ)}ᵀ b = + ∑ h : Fin 3 → Fin 1 ⊕ Fin 3, + (Tensor.basis _).repr ε4 (Fin.snoc h (b 0)) * + (Tensor.basis _).repr ({ε4 | τ(μ) τ(ν) τ(ρ) τ(σ)}ᵀ) (Fin.snoc h (b 1)) := by + have route {cA cB : Fin 4 → Color} + (h1 : (0 : Fin 4) ≠ 2) (h2 : (1 : Fin 6) ≠ 4) (h3 : (2 : Fin 8) ≠ 6) + (b : Fin 2 → Fin 1 ⊕ Fin 3) (x0 x1 x2 : Fin 1 ⊕ Fin 3) : + let v := (ofFinEquiv (S := realLorentzTensor 3) (c := Fin.append cA cB) h3 + ((ofFinEquiv h2 ((ofFinEquiv h1 b (x0, x0)).1) (x1, x1)).1) (x2, x2)).1 + (ComponentIdx.prod (S := realLorentzTensor 3) (c := cA) (c1 := cB)) v = + (![x0, x1, x2, b 0], ![x0, x1, x2, b 1]) := by + dsimp only + apply Prod.ext <;> funext m <;> fin_cases m <;> rfl + simp only [contrT_basis_repr_apply_eq_fin, prodT_basis_repr_apply, + route] + let F (h : Fin 3 → Fin 1 ⊕ Fin 3) := + (Tensor.basis _).repr ε4 ![h 0, h 1, h 2, b 0] * + (Tensor.basis _).repr ({ε4 | τ(μ) τ(ν) τ(ρ) τ(σ)}ᵀ) ![h 0, h 1, h 2, b 1] + change (∑ x0, ∑ x1, ∑ x2, F ![x0, x1, x2]) = _ + let e : ((Fin 1 ⊕ Fin 3) × (Fin 1 ⊕ Fin 3) × (Fin 1 ⊕ Fin 3)) ≃ + (Fin 3 → Fin 1 ⊕ Fin 3) := + { toFun := fun p => ![p.1, p.2.1, p.2.2] + invFun := fun v => (v 0, v 1, v 2) + left_inv := fun _ => rfl + right_inv := fun v => by funext m; fin_cases m <;> rfl } + calc + _ = ∑ h, F h := by + rw [← Equiv.sum_comp e F] + simp only [Fintype.sum_prod_type] + rfl + _ = _ := by + refine Finset.sum_congr rfl fun h _ => ?_ + dsimp only [F] + have hs (y : Fin 1 ⊕ Fin 3) : + (![h 0, h 1, h 2, y] : Fin 4 → Fin 1 ⊕ Fin 3) = Fin.snoc h y := by + funext i + fin_cases i <;> rfl + rw [hs (b 0), hs (b 1)] + +/-! + +### B.1. The epsilon-epsilon contraction identities + +-/ + +/-- Contracting three indices of the Lorentzian Levi-Civita tensor with a fully lowered copy gives +`-6` times the mixed-index unit tensor. -/ +lemma leviCivita_contract_three : {ε4 | μ ν ρ σ ⊗ ε4 | τ(μ) τ(ν) τ(ρ) τ(τ) = + (-6) • unitTensor (S := realLorentzTensor) Color.down | σ τ }ᵀ := by + apply (Tensor.basis _).repr.injective + ext b + simp only [map_zsmul, Finsupp.coe_smul, Pi.smul_apply, zsmul_eq_mul, + permT_basis_repr_symm_apply, basisIdxCongr_eq_refl, Equiv.refl_apply, + unitTensor_repr_apply Color.down] + rw [IsReindexing.inv_eq_self_of_pointwise_eq _ (by decide), + IsReindexing.inv_eq_self_of_pointwise_eq _ (by decide)] + norm_num + rw [leviCivita_contract_three_basis_repr_apply] + calc + _ = - ∑ h : Fin 3 → Fin 1 ⊕ Fin 3, + (Tensor.basis _).repr ε4 (Fin.snoc h (b 0)) * + (Tensor.basis _).repr ε4 (Fin.snoc h (b 1)) := by + rw [← Finset.sum_neg_distrib] + refine Finset.sum_congr rfl fun h _ => ?_ + rw [leviCivita_lowered_basis_repr_apply] + ring + _ = - (6 * (if b 0 = b 1 then 1 else 0)) := by + rw [leviCivita_basis_contract_three] + _ = (if b 0 = b 1 then -6 else 0) := by + split_ifs <;> norm_num + +-- `checkType` linter: whnf on these full-contraction tensor-notation statements exceeds the +-- linter's 200k-heartbeat budget (since the v4.32.0 bump; still the case on v4.33.0). The proofs +-- themselves elaborate within the default budget. +/-- Fully contracting the tensor product of `ε4` and its fully lowered form is the sum of the +products of their matching standard-basis components. -/ +@[nolint checkType] +lemma leviCivita_contract_self_eq_sum : + {ε4 | μ ν ρ σ ⊗ ε4 | τ(μ) τ(ν) τ(ρ) τ(σ)}ᵀ.toField = + ∑ b : ComponentIdx (S := realLorentzTensor 3) + ![Color.up, Color.up, Color.up, Color.up], + (Tensor.basis _).repr ε4 b * + (Tensor.basis _).repr ({ε4 | τ(μ) τ(ν) τ(ρ) τ(σ)}ᵀ) b := by + have route {cA cB : Fin 4 → Color} + (h1 : (0 : Fin 2) ≠ 1) (h2 : (1 : Fin 4) ≠ 3) + (h3 : (2 : Fin 6) ≠ 5) (h4 : (3 : Fin 8) ≠ 7) + (x0 x1 x2 x3 : Fin 1 ⊕ Fin 3) : + let v := (ofFinEquiv (S := realLorentzTensor 3) (c := Fin.append cA cB) h4 + ((ofFinEquiv h3 + ((ofFinEquiv h2 + ((ofFinEquiv h1 (fun j => j.elim0) (x0, x0)).1) (x1, x1)).1) (x2, x2)).1) + (x3, x3)).1 + (ComponentIdx.prod (S := realLorentzTensor 3) (c := cA) (c1 := cB)) v = + (![x0, x1, x2, x3], ![x0, x1, x2, x3]) := by + dsimp only + apply Prod.ext <;> funext m <;> fin_cases m <;> rfl + rw [Tensor.toField_eq_repr] + simp only [contrT_basis_repr_apply_eq_fin, prodT_basis_repr_apply, + route] + let F (b : Fin 4 → Fin 1 ⊕ Fin 3) := (Tensor.basis _).repr ε4 b * + (Tensor.basis _).repr ({ε4 | τ(μ) τ(ν) τ(ρ) τ(σ)}ᵀ) b + change (∑ x0, ∑ x1, ∑ x2, ∑ x3, F ![x0, x1, x2, x3]) = ∑ b, F b + let e : ((Fin 1 ⊕ Fin 3) × (Fin 1 ⊕ Fin 3) × (Fin 1 ⊕ Fin 3) × (Fin 1 ⊕ Fin 3)) ≃ + (Fin 4 → Fin 1 ⊕ Fin 3) := + { toFun := fun p => ![p.1, p.2.1, p.2.2.1, p.2.2.2] + invFun := fun v => (v 0, v 1, v 2, v 3) + left_inv := fun _ => rfl + right_inv := fun v => by funext m; fin_cases m <;> rfl } + rw [← Equiv.sum_comp e F] + simp only [Fintype.sum_prod_type] + rfl + +/-- Fully contracting the Lorentzian Levi-Civita tensor with a lowered copy gives `-24`. -/ +@[nolint checkType] +lemma leviCivita_contract_self : + {ε4 | μ ν ρ σ ⊗ ε4 | τ(μ) τ(ν) τ(ρ) τ(σ)}ᵀ.toField = - 24 := by + rw [leviCivita_contract_self_eq_sum] + calc + _ = - ∑ b : ComponentIdx (S := realLorentzTensor 3) + ![Color.up, Color.up, Color.up, Color.up], + (Tensor.basis _).repr ε4 b * (Tensor.basis _).repr ε4 b := by + rw [← Finset.sum_neg_distrib] + refine Finset.sum_congr rfl fun b _ => ?_ + rw [leviCivita_lowered_basis_repr_apply] + ring + _ = -24 := by rw [leviCivita_basis_contract_self] + +end realLorentzTensor diff --git a/Physlib/Relativity/Tensors/MetricTensor.lean b/Physlib/Relativity/Tensors/MetricTensor.lean index 23483e0ee5..57b7139e81 100644 --- a/Physlib/Relativity/Tensors/MetricTensor.lean +++ b/Physlib/Relativity/Tensors/MetricTensor.lean @@ -29,6 +29,13 @@ open Tensor noncomputable def metricTensor (c : C) : S.Tensor ![c, c] := fromConstPair (S.metric c) +/-- A component of the metric tensor is the corresponding component of the metric intertwiner +in the tensor-product basis. -/ +lemma metricTensor_basis_repr (c : C) (φ : ComponentIdx (S := S) ![c, c]) : + (Tensor.basis _).repr (metricTensor (S := S) c) φ = + (Module.Basis.tensorProduct (b c) (b c)).repr ((S.metric c) (1 : k)) (φ 0, φ 1) := by + rw [metricTensor, fromConstPair, fromPairT_basis_repr] + lemma metricTensor_congr {c c1 : C} (h : c = c1) : S.metricTensor c = permT id (by simp [h]) (metricTensor c1) := by subst h diff --git a/Physlib/Relativity/Tensors/RealTensor/Contraction/CrossToEnd.lean b/Physlib/Relativity/Tensors/RealTensor/Contraction/CrossToEnd.lean new file mode 100644 index 0000000000..c66371f1fa --- /dev/null +++ b/Physlib/Relativity/Tensors/RealTensor/Contraction/CrossToEnd.lean @@ -0,0 +1,96 @@ +/- +Copyright (c) 2026 Robert Sneiderman. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Sneiderman +-/ +module + +public import Physlib.Relativity.Tensors.RealTensor.Basic +/-! + +# Components of real Lorentz cross contractions + +## i. Overview + +This file gives the standard-basis component formula for `crossToEnd` on real Lorentz tensors. +The contraction pairing of the standard covariant and contravariant bases reduces the generic +component expansion to one finite sum. + +## ii. Key results + +- `realLorentzTensor.crossToEnd_basis_repr_apply_eq_fin` expresses each component of a cross + contraction as a sum over the contracted Lorentz index. + +## iii. Table of contents + +- A. Basis components + +## iv. References + +-/ + +@[expose] public section + +noncomputable section + +namespace realLorentzTensor + +open TensorSpecies Tensor + +/-! + +## A. Basis components + +-/ + +/-- For real Lorentz tensors, the component formula for `crossToEnd` collapses to one sum because +the standard contravariant and covariant bases are dual under contraction. -/ +lemma crossToEnd_basis_repr_apply_eq_fin {d nA nB : ℕ} {cA : Fin (nA + 1) → Color} + {cB : Fin (nB + 1) → Color} (i : Fin (nA + 1)) (j : Fin (nB + 1)) + (hc : (realLorentzTensor d).τ (cA i) = cB j) (t : ℝT(d, cA)) (M : ℝT(d, cB)) + (φ : ComponentIdx (S := realLorentzTensor d) + (Fin.append (cA ∘ i.succAbove) (cB ∘ j.succAbove))) : + (Tensor.basis _).repr (crossToEnd i j hc t M) φ = + ∑ x : Fin 1 ⊕ Fin d, + (Tensor.basis cA).repr t (i.insertNth x (fun m => φ (Fin.castAdd nB m))) * + (Tensor.basis cB).repr M (j.insertNth x (fun m => φ (Fin.natAdd nA m))) := by + rw [crossToEnd] + simp only [LinearMap.compr₂_apply, LinearMap.comp_apply] + rw [permT_basis_repr_symm_apply, contrT_basis_repr_apply_eq_fin] + conv_lhs => enter [2, x]; rw [permT_basis_repr_symm_apply, prodT_basis_repr_apply] + simp only [basisIdxCongr_eq_refl, Equiv.refl_apply] + refine Finset.sum_congr rfl fun x _ => ?_ + simp only [ComponentIdx.prod, Equiv.coe_fn_mk, basisIdxCongr_eq_refl, Equiv.refl_apply] + congr 1 + · congr 1 + funext m + rw [IsReindexing.inv_cast_eq] + induction m using Fin.succAboveCases (i := i) with + | x => + rw [Fin.insertNth_apply_same] + exact ComponentIdx.DropPairSection.ofFinEquiv_apply_fst _ _ _ + | p q => + rw [Fin.insertNth_apply_succAbove] + conv_lhs => rw [← Fin.succSuccAbove_castAdd_natAdd_apply_castAdd i j q] + simp only [Fin.cast_cast, Fin.cast_eq_self] + rw [(ComponentIdx.DropPairSection.mem_iff_apply_succSuccAbove_eq _ _).mp + (ComponentIdx.DropPairSection.ofFinEquiv _ _ _).2] + simp only [basisIdxCongr_eq_refl, Equiv.refl_apply] + exact congrArg φ (IsReindexing.inv_id_eq _ _) + · congr 1 + funext m + rw [IsReindexing.inv_cast_eq] + induction m using Fin.succAboveCases (i := j) with + | x => + rw [Fin.insertNth_apply_same] + exact ComponentIdx.DropPairSection.ofFinEquiv_apply_snd _ _ _ + | p q => + rw [Fin.insertNth_apply_succAbove] + conv_lhs => rw [← Fin.succSuccAbove_castAdd_natAdd_apply_natAdd i j q] + simp only [Fin.cast_cast, Fin.cast_eq_self] + rw [(ComponentIdx.DropPairSection.mem_iff_apply_succSuccAbove_eq _ _).mp + (ComponentIdx.DropPairSection.ofFinEquiv _ _ _).2] + simp only [basisIdxCongr_eq_refl, Equiv.refl_apply] + exact congrArg φ (IsReindexing.inv_id_eq _ _) + +end realLorentzTensor diff --git a/Physlib/Relativity/Tensors/RealTensor/Metrics/Basic.lean b/Physlib/Relativity/Tensors/RealTensor/Metrics/Basic.lean index 61061f0d79..f006a6b3be 100644 --- a/Physlib/Relativity/Tensors/RealTensor/Metrics/Basic.lean +++ b/Physlib/Relativity/Tensors/RealTensor/Metrics/Basic.lean @@ -1,12 +1,11 @@ /- Copyright (c) 2024 Joseph Tooby-Smith. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Joseph Tooby-Smith +Authors: Robert Sneiderman, Joseph Tooby-Smith -/ module -public import Physlib.Relativity.Tensors.RealTensor.Basic -public import Physlib.Relativity.Tensors.MetricTensor +public import Physlib.Relativity.Tensors.RealTensor.Contraction.CrossToEnd /-! ## Metrics as real Lorentz tensors @@ -97,7 +96,7 @@ lemma actionT_contrMetric {d} (g : LorentzGroup d) : g • η d = η d := by /- -## There value with respect to a basis +## Their value with respect to a basis -/ @@ -105,7 +104,11 @@ lemma coMetric_repr_apply_eq_minkowskiMatrix {d : ℕ} (b : ComponentIdx (S := realLorentzTensor d) ![Color.down, Color.down]) : (Tensor.basis _).repr (coMetric d) b = minkowskiMatrix (b 0) (b 1) := by - rw [coMetric_eq_fromPairT, fromPairT_basis_repr, + change (Tensor.basis _).repr + (metricTensor (S := realLorentzTensor d) Color.down) b = _ + rw [metricTensor_basis_repr, + show ((realLorentzTensor d).metric Color.down) (1 : ℝ) = Lorentz.preCoMetricVal d from + Lorentz.preCoMetric_apply_one, Lorentz.preCoMetricVal_expand_tmul_minkowskiMatrix] simp only [map_sum, Finsupp.coe_finsetSum, Finset.sum_apply, map_smul, Finsupp.coe_smul, Pi.smul_apply, Basis.tensorProduct_repr_tmul_apply, Basis.repr_self, Finsupp.single_apply, @@ -117,7 +120,11 @@ lemma contrMetric_repr_apply_eq_minkowskiMatrix {d : ℕ} (b : ComponentIdx (S := realLorentzTensor d) ![Color.up, Color.up]) : (Tensor.basis _).repr (contrMetric d) b = minkowskiMatrix (b 0) (b 1) := by - rw [contrMetric_eq_fromPairT, fromPairT_basis_repr, + change (Tensor.basis _).repr + (metricTensor (S := realLorentzTensor d) Color.up) b = _ + rw [metricTensor_basis_repr, + show ((realLorentzTensor d).metric Color.up) (1 : ℝ) = Lorentz.preContrMetricVal d from + Lorentz.preContrMetric_apply_one, Lorentz.preContrMetricVal_expand_tmul_minkowskiMatrix] simp only [map_sum, Finsupp.coe_finsetSum, Finset.sum_apply, map_smul, Finsupp.coe_smul, Pi.smul_apply, Basis.tensorProduct_repr_tmul_apply, Basis.repr_self, Finsupp.single_apply, @@ -125,4 +132,60 @@ lemma contrMetric_repr_apply_eq_minkowskiMatrix {d : ℕ} rw [Finset.sum_eq_single (b 0)] <;> simp +contextual [minkowskiMatrix.as_diagonal, Matrix.diagonal_apply] +/-- The component matrix of either real Lorentz metric tensor is the Minkowski matrix. -/ +lemma metricTensor_repr_apply_eq_minkowskiMatrix {d : ℕ} (c : Color) + (φ : ComponentIdx (S := realLorentzTensor d) ![c, c]) : + (Tensor.basis _).repr (metricTensor (S := realLorentzTensor d) c) φ = + minkowskiMatrix (φ 0) (φ 1) := by + cases c with + | up => exact contrMetric_repr_apply_eq_minkowskiMatrix φ + | down => exact coMetric_repr_apply_eq_minkowskiMatrix φ + +set_option backward.isDefEq.respectTransparency false in +/-- Raising or lowering one index of a real Lorentz tensor contracts that slot's components with +the Minkowski matrix. -/ +lemma toDualMapAtIndex_basis_repr_apply {d n : ℕ} {c : Fin (n + 1) → Color} + (i : Fin (n + 1)) (t : ℝT(d, c)) + (φ : ComponentIdx (S := realLorentzTensor d) + (Function.update c i ((realLorentzTensor d).τ (c i)))) : + (Tensor.basis _).repr (Tensor.toDualMapAtIndex (S := realLorentzTensor d) i t) φ = + ∑ x : Fin 1 ⊕ Fin d, + (Tensor.basis c).repr t (i.insertNth x (fun m => φ (i.succAbove m))) * + minkowskiMatrix x (φ i) := by + have h := crossToSlot_basis_repr_apply (S := realLorentzTensor d) i (0 : Fin 2) rfl + (metricTensor (S := realLorentzTensor d) ((realLorentzTensor d).τ (c i))) t φ + rw [crossToEnd_basis_repr_apply_eq_fin] at h + simp only [basisIdxCongr_eq_refl, Equiv.refl_apply] at h + refine h.trans (Finset.sum_congr rfl fun x _ => ?_) + congr 1 + · congr 1 + funext m + induction m using Fin.succAboveCases (i := i) with + | x => rw [Fin.insertNth_apply_same, Fin.insertNth_apply_same] + | p q => + rw [Fin.insertNth_apply_succAbove, Fin.insertNth_apply_succAbove, + IsReindexing.inv_equiv_symm_eq, + ← Fin.append_succAbove_const_eq_cycleIcc i, Fin.append_left] + rw [metricTensor_repr_apply_eq_minkowskiMatrix] + congr 1 + rw [show (1 : Fin 2) = (0 : Fin 2).succAbove 0 from rfl, + Fin.insertNth_apply_succAbove, IsReindexing.inv_equiv_symm_eq, + ← Fin.append_succAbove_const_eq_cycleIcc i, Fin.append_right] + +/-- In the standard Lorentz basis, raising or lowering an index multiplies the component with that +index fixed by the corresponding diagonal entry of the Minkowski metric. -/ +lemma toDualMapAtIndex_basis_repr_apply_eq_mul {d n : ℕ} {c : Fin (n + 1) → Color} + (i : Fin (n + 1)) (t : ℝT(d, c)) + (φ : ComponentIdx (S := realLorentzTensor d) + (Function.update c i ((realLorentzTensor d).τ (c i)))) : + (Tensor.basis _).repr (Tensor.toDualMapAtIndex (S := realLorentzTensor d) i t) φ = + (Tensor.basis c).repr t φ * minkowskiMatrix (φ i) (φ i) := by + rw [toDualMapAtIndex_basis_repr_apply] + change ((fun x => (Tensor.basis c).repr t + (i.insertNth x (fun m => φ (i.succAbove m)))) ᵥ* minkowskiMatrix) (φ i) = _ + rw [minkowskiMatrix.vecMul_apply] + congr 2 + change (i.insertNth (φ i) (i.removeNth φ) : Fin (n + 1) → Fin 1 ⊕ Fin d) = φ + exact Fin.insertNth_self_removeNth i φ + end realLorentzTensor diff --git a/Physlib/Relativity/Tensors/RealTensor/Units/Basic.lean b/Physlib/Relativity/Tensors/RealTensor/Units/Basic.lean new file mode 100644 index 0000000000..da42139ec6 --- /dev/null +++ b/Physlib/Relativity/Tensors/RealTensor/Units/Basic.lean @@ -0,0 +1,74 @@ +/- +Copyright (c) 2026 Robert Sneiderman. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Sneiderman +-/ +module + +public import Physlib.Relativity.Tensors.RealTensor.Basic +/-! + +# Unit tensors for real Lorentz tensors + +## i. Overview + +This file computes the unit tensors of `realLorentzTensor` in the standard covariant and +contravariant bases. + +## ii. Key results + +- `realLorentzTensor.unitTensor_repr_apply` identifies the standard-basis components of either + real Lorentz unit tensor with the Kronecker delta. + +## iii. Table of contents + +- A. Basis components + +## iv. References + +-/ + +@[expose] public section + +open Module TensorProduct + +noncomputable section + +namespace realLorentzTensor + +open TensorSpecies Tensor + +/-! + +## A. Basis components + +-/ + +set_option backward.isDefEq.respectTransparency false in +/-- In the standard contravariant and covariant bases, either real Lorentz unit tensor has +Kronecker-delta components. -/ +lemma unitTensor_repr_apply {d : ℕ} (c : Color) + (φ : ComponentIdx (S := realLorentzTensor d) ![(realLorentzTensor d).τ c, c]) : + (Tensor.basis _).repr (unitTensor (S := realLorentzTensor d) c) φ = + if φ 0 = φ 1 then 1 else 0 := by + cases c with + | up => + rw [unitTensor_basis_repr, + show ((realLorentzTensor d).unit Color.up) (1 : ℝ) = Lorentz.preCoContrUnitVal d from + Lorentz.preCoContrUnit_apply_one] + simp only [τ_up_eq_down] + rw [Lorentz.preCoContrUnitVal_expand_tmul] + simp only [map_sum, Finsupp.coe_finsetSum, Finset.sum_apply, + Basis.tensorProduct_repr_tmul_apply, Basis.repr_self, Finsupp.single_apply, smul_eq_mul] + rw [Finset.sum_eq_single (φ 0)] <;> aesop + | down => + rw [unitTensor_basis_repr, + show ((realLorentzTensor d).unit Color.down) (1 : ℝ) = Lorentz.preContrCoUnitVal d from + Lorentz.preContrCoUnit_apply_one] + simp only [τ_down_eq_up] + rw [Lorentz.preContrCoUnitVal_expand_tmul] + simp only [map_sum, Finsupp.coe_finsetSum, Finset.sum_apply, + Basis.tensorProduct_repr_tmul_apply, Basis.repr_self, Finsupp.single_apply, smul_eq_mul] + rw [Finset.sum_eq_single (φ 0)] <;> aesop + +end realLorentzTensor diff --git a/Physlib/Relativity/Tensors/Reindexing.lean b/Physlib/Relativity/Tensors/Reindexing.lean index c8c92e692c..e4d4195b0b 100644 --- a/Physlib/Relativity/Tensors/Reindexing.lean +++ b/Physlib/Relativity/Tensors/Reindexing.lean @@ -143,6 +143,32 @@ lemma inv_apply_apply {n m : ℕ} {c : Fin n → C} {c1 : Fin m → C} change h.toEquiv.symm (h.toEquiv x) = x simp +lemma inv_eq_self_of_pointwise_eq {n : ℕ} {c c1 : Fin n → C} {σ : Fin n → Fin n} + (h : IsReindexing c c1 σ) (hσ : ∀ x, σ x = x) (x : Fin n) : + h.inv σ x = x := by + have hx := h.inv_apply_apply σ x + rw [hσ] at hx + exact hx + +lemma inv_id_eq {n : ℕ} {c c1 : Fin n → C} + (h : IsReindexing c c1 (id : Fin n → Fin n)) (x : Fin n) : + h.inv (id : Fin n → Fin n) x = x := + h.inv_apply_apply (id : Fin n → Fin n) x + +lemma inv_cast_eq {n m : ℕ} {c : Fin n → C} {c1 : Fin m → C} (e : m = n) + (h : IsReindexing c c1 (Fin.cast e)) (x : Fin n) : + h.inv (Fin.cast e) x = Fin.cast e.symm x := by + have hx := h.inv_apply_apply (Fin.cast e) x + have hval : (h.inv (Fin.cast e) x).val = x.val := congrArg Fin.val hx + exact Fin.val_inj.mp hval + +lemma inv_equiv_symm_eq {n : ℕ} {c c1 : Fin n → C} (e : Equiv.Perm (Fin n)) + (h : IsReindexing c c1 ⇑e.symm) (x : Fin n) : + h.inv ⇑e.symm x = e x := by + have hx := h.inv_apply_apply ⇑e.symm x + apply e.symm.injective + rw [hx, Equiv.symm_apply_apply] + lemma preserve_color {n m : ℕ} {c : Fin n → C} {c1 : Fin m → C} {σ : Fin m → Fin n} (h : IsReindexing c c1 σ) : ∀ (x : Fin m), c1 x = (c ∘ σ) x := by diff --git a/Physlib/Relativity/Tensors/UnitTensor.lean b/Physlib/Relativity/Tensors/UnitTensor.lean index df50a06907..1acf47e278 100644 --- a/Physlib/Relativity/Tensors/UnitTensor.lean +++ b/Physlib/Relativity/Tensors/UnitTensor.lean @@ -29,6 +29,14 @@ open Tensor noncomputable def unitTensor (c : C) : S.Tensor ![S.τ c, c] := fromConstPair (S.unit c) +/-- A component of the unit tensor is the corresponding component of the unit intertwiner in the +tensor-product basis. -/ +lemma unitTensor_basis_repr (c : C) (φ : ComponentIdx (S := S) ![S.τ c, c]) : + (Tensor.basis _).repr (unitTensor (S := S) c) φ = + (Module.Basis.tensorProduct (b (S.τ c)) (b c)).repr ((S.unit c) (1 : k)) + (φ 0, φ 1) := by + rw [unitTensor, fromConstPair, fromPairT_basis_repr] + lemma unitTensor_congr {c c1 : C} (h : c = c1) : unitTensor c = permT id (by simp [h]) (unitTensor (S := S) c1) := by subst h From 1b734ea0a474a5f907cb214342566e92afd1dd47 Mon Sep 17 00:00:00 2001 From: Alex-Zughaid <117576511+Alex-Zughaid@users.noreply.github.com> Date: Thu, 27 Aug 2026 12:42:04 +0100 Subject: [PATCH 17/20] Enable Lake's built-in artifact cache enableArtifactCache makes a build populate Lake's own content-addressed cache; restoreAllArtifacts materialises cached artifacts back into .lake/build. Both are needed for the CI publish/consume flow in the next commit, and they help locally too: switching branches reuses cached work instead of recompiling. --- lakefile.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lakefile.toml b/lakefile.toml index 4f380c0a02..7f85bae95d 100644 --- a/lakefile.toml +++ b/lakefile.toml @@ -1,4 +1,15 @@ name = "Physlib" +# Lake's artifact cache. `enableArtifactCache` makes a build populate the +# cache, which is what CI uploads to R2 and what lets a local build be reused +# after switching branches. `restoreAllArtifacts` is the other direction: +# materialising cached artifacts back into .lake/build, which is how a +# contributor benefits from `lake cache get`. +# +# These are package settings rather than environment variables -- an env var +# does not enable the cache, and `lake build -o` warns that mappings it emits +# will not be backed by cached artifacts without this. +enableArtifactCache = true +restoreAllArtifacts = true defaultTargets = ["Physlib", "QuantumInfo"] [[require]] From 7f726f7412c2aee60e39e97f457d131d140bdc80 Mon Sep 17 00:00:00 2001 From: Alex-Zughaid <117576511+Alex-Zughaid@users.noreply.github.com> Date: Thu, 27 Aug 2026 12:42:25 +0100 Subject: [PATCH 18/20] CI: publish Physlib's build cache to R2 on every push to master New contributors currently compile all of Physlib, QuantumInfo, and PhyslibAlpha from source on first build. lake exe cache get already solves this for Mathlib; nothing did for Physlib's own code. Both workflows now build with Lake's artifact cache enabled, emit input-to-output mappings, stage them, and upload with `lake cache put-staged` to a Cloudflare R2 bucket (lake-cache.toml). R2 rather than S3 or Azure because the cost that scales here is egress -- every contributor download -- and R2 charges nothing for it at any volume. lake-cache.toml is committed rather than generated in the workflow, since its endpoints are public information and committing it means local contributors get the cache with no manual configuration. It declares separate read (physlib-r2) and write (physlib-r2-upload) services, so a contributor cannot write to the cache even by accident. Publishing is gated on the LAKE_CACHE_KEY secret being present and never blocks the rest of the build if it fails -- a stale or unreachable cache is an inconvenience, not a correctness problem. --- .github/workflows/alphaBuild.yml | 41 ++++++++++++++++++++++++ .github/workflows/build.yml | 53 ++++++++++++++++++++++++++++++++ lake-cache.toml | 48 +++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 lake-cache.toml diff --git a/.github/workflows/alphaBuild.yml b/.github/workflows/alphaBuild.yml index 28e8d64a3f..87a638fd9d 100644 --- a/.github/workflows/alphaBuild.yml +++ b/.github/workflows/alphaBuild.yml @@ -13,6 +13,9 @@ jobs: alpha_build: name: Lean based style linters runs-on: ubuntu-latest + env: + LAKE_CACHE_DIR: .lake/cache + HAVE_CACHE_KEY: ${{ secrets.LAKE_CACHE_KEY != '' }} steps: - uses: actions/checkout@v4 @@ -41,6 +44,44 @@ jobs: run: | bash -o pipefail -c "env LEAN_ABORT_ON_PANIC=1 lake build -KCI PhyslibAlpha | tee stdout.log" + # PhyslibAlpha is not a default target, so build.yml never builds or + # publishes it. Same R2 bucket and scope as build.yml -- artifacts are + # content-addressed by hash, so the two workflows' uploads coexist + # without needing separate scopes. + - name: stage build outputs for the cache + id: stage + if: github.event_name == 'push' && env.HAVE_CACHE_KEY == 'true' + continue-on-error: true + run: | + set -euo pipefail + mkdir -p ../lake-cache-staging + lake build --no-build -KCI PhyslibAlpha -o .lake/outputs.jsonl + echo "mappings: $(wc -l < .lake/outputs.jsonl) entries" + lake cache stage .lake/outputs.jsonl ../lake-cache-staging + echo "staged: $(find ../lake-cache-staging -name '*.ltar' | wc -l) ltar files" + + - name: publish to R2 cache + id: publish + if: github.event_name == 'push' && env.HAVE_CACHE_KEY == 'true' && steps.stage.outcome == 'success' + continue-on-error: true + env: + LAKE_CACHE_KEY_RAW: ${{ secrets.LAKE_CACHE_KEY }} + LAKE_CONFIG: ${{ github.workspace }}/lake-cache.toml + run: | + set -euo pipefail + KEY="$(printf %s "$LAKE_CACHE_KEY_RAW" | sed -e 's/[[:space:]]*$//')" + echo "::add-mask::$KEY" + export LAKE_CACHE_KEY="$KEY" + + lake cache put-staged ../lake-cache-staging \ + --scope=physlib-master \ + --rev="${{ github.sha }}" \ + --toolchain="$(cat lean-toolchain)" + + - name: warn if cache publish failed + if: github.event_name == 'push' && env.HAVE_CACHE_KEY == 'true' && (steps.stage.outcome == 'failure' || steps.publish.outcome == 'failure') + run: echo "::warning::PhyslibAlpha build cache was not published this run." + - name: runLinter on PhyslibAlpha if: ${{ always() && steps.build.outcome == 'success' || steps.build.outcome == 'failure' }} id: lint diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dbb17cdb1e..3b6616252c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,13 @@ jobs: doc_lint: name: Lean based style linters runs-on: ubuntu-latest + env: + # Keep Lake's artifact cache inside the workspace rather than the + # toolchain directory, so it is scoped to this run and easy to stage. + LAKE_CACHE_DIR: .lake/cache + # Job-level so the cache steps can gate on it: a step's own `env:` block + # is not visible to that step's `if:` condition. + HAVE_CACHE_KEY: ${{ secrets.LAKE_CACHE_KEY != '' }} steps: - uses: actions/checkout@v4 @@ -41,6 +48,52 @@ jobs: run: | bash -o pipefail -c "env LEAN_ABORT_ON_PANIC=1 lake build -KCI | tee stdout.log" + # Publish to the R2 bucket via Lake's own content-addressed cache. + # Skipped entirely until LAKE_CACHE_KEY exists, so CI keeps working + # before the bucket is provisioned. See docs/cache-setup.md. + # + # continue-on-error on both steps: a stale or unreachable cache is an + # inconvenience, not a correctness problem, and must never block a + # merge. A failure here is surfaced via the warning step below instead. + - name: stage build outputs for the cache + id: stage + if: github.event_name == 'push' && env.HAVE_CACHE_KEY == 'true' + continue-on-error: true + run: | + set -euo pipefail + mkdir -p ../lake-cache-staging + # `--no-build` here does not build anything; it emits the + # input-to-output mappings for what was just built. + lake build --no-build -KCI -o .lake/outputs.jsonl + echo "mappings: $(wc -l < .lake/outputs.jsonl) entries" + lake cache stage .lake/outputs.jsonl ../lake-cache-staging + echo "staged: $(find ../lake-cache-staging -name '*.ltar' | wc -l) ltar files" + + - name: publish to R2 cache + id: publish + if: github.event_name == 'push' && env.HAVE_CACHE_KEY == 'true' && steps.stage.outcome == 'success' + continue-on-error: true + env: + LAKE_CACHE_KEY_RAW: ${{ secrets.LAKE_CACHE_KEY }} + LAKE_CONFIG: ${{ github.workspace }}/lake-cache.toml + run: | + set -euo pipefail + # GitHub secrets commonly carry a trailing newline, which breaks the + # SigV4 signature. Trim it, and mask the value so it cannot surface + # in logs. + KEY="$(printf %s "$LAKE_CACHE_KEY_RAW" | sed -e 's/[[:space:]]*$//')" + echo "::add-mask::$KEY" + export LAKE_CACHE_KEY="$KEY" + + lake cache put-staged ../lake-cache-staging \ + --scope=physlib-master \ + --rev="${{ github.sha }}" \ + --toolchain="$(cat lean-toolchain)" + + - name: warn if cache publish failed + if: github.event_name == 'push' && env.HAVE_CACHE_KEY == 'true' && (steps.stage.outcome == 'failure' || steps.publish.outcome == 'failure') + run: echo "::warning::Physlib build cache was not published this run (staging or upload failed). Contributors will fall back to compiling from source until the next successful push." + - name: check file imports run: | bash -o pipefail -c "env LEAN_ABORT_ON_PANIC=1 lake exe check_file_imports" diff --git a/lake-cache.toml b/lake-cache.toml new file mode 100644 index 0000000000..c28ee6f9ce --- /dev/null +++ b/lake-cache.toml @@ -0,0 +1,48 @@ +# Lake cache configuration for Physlib. +# +# Point Lake at this file with LAKE_CONFIG, e.g. +# +# LAKE_CONFIG=$PWD/lake-cache.toml lake cache get +# +# `scripts/get-cache.sh` does that for you. Bucket endpoints are public +# information, which is why this file is committed; the credential that +# authorises uploads is NOT here. It is supplied via the LAKE_CACHE_KEY +# environment variable, held as an encrypted GitHub Actions secret. +# +# Two services are defined because reads and writes need different access: +# +# physlib-r2 anonymous, public read endpoint. What contributors and +# `lake cache get` use. No credential required. +# physlib-r2-upload authenticated S3 endpoint, used only by CI to publish. +# Requires LAKE_CACHE_KEY. +# +# Keeping them separate means a contributor can never accidentally write to +# the cache, and mirrors how Mathlib separates its read and write paths. +# +# --------------------------------------------------------------------------- +# Both endpoints point at the physlib-cache bucket. Reads go through its +# Public Development URL. +# +# NOTE: Cloudflare rate-limits r2.dev and states it is not for production +# traffic. It is fine while this is being trialled, but a cache fetched by +# every contributor on every clone is production traffic. Attaching a custom +# domain to the bucket and swapping the read endpoint below is the fix, and +# is worth doing before this is announced widely. See docs/cache-setup.md. +# --------------------------------------------------------------------------- + +cache.defaultService = "physlib-r2" +cache.defaultUploadService = "physlib-r2-upload" + +# Anonymous read path used by contributors. +[[cache.service]] +name = "physlib-r2" +kind = "s3" +artifactEndpoint = "https://pub-d99ac65acb8049f9a3bd606be7205f1c.r2.dev/artifacts" +revisionEndpoint = "https://pub-d99ac65acb8049f9a3bd606be7205f1c.r2.dev/revisions" + +# Authenticated write path used by CI only. Same bucket, S3 API endpoint. +[[cache.service]] +name = "physlib-r2-upload" +kind = "s3" +artifactEndpoint = "https://305f4708d1749d8e1873f7a629768540.r2.cloudflarestorage.com/physlib-cache/artifacts" +revisionEndpoint = "https://305f4708d1749d8e1873f7a629768540.r2.cloudflarestorage.com/physlib-cache/revisions" From 034139c4de5a2964d579ac5ee41676653fd59ddd Mon Sep 17 00:00:00 2001 From: Alex-Zughaid <117576511+Alex-Zughaid@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:10:09 +0100 Subject: [PATCH 19/20] Add scripts/get_cache.lean: fetch the build cache in one command Downloads Mathlib's prebuilt files (via Mathlib's own lake exe cache get) and Physlib's own (via lake cache get against the R2 bucket), so a contributor runs one command instead of two before their first lake build. A Lean lean_exe rather than a shell script, matching every other tool in scripts/ (check_file_imports, sorry_lint, style_lint, etc) -- bash was the odd one out. Only imports Lean core, not Mathlib, so building it does not need Mathlib already built. Safe to run at any time and safe to skip: any failure warns and exits 0, falling back to a normal build from source. Unrecognised flags are rejected up front rather than silently ignored, so a typo'd flag does not fall through into running the full fetch unexpectedly. --- lakefile.toml | 4 ++ scripts/get_cache.lean | 90 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 scripts/get_cache.lean diff --git a/lakefile.toml b/lakefile.toml index 7f85bae95d..6a8a8476da 100644 --- a/lakefile.toml +++ b/lakefile.toml @@ -34,6 +34,10 @@ moreLeanArgs = ["-Dwarn.sorry=false", "-Dweak.says.verify=true"] name = "QuantumInfo" moreLeanArgs = ["-Dwarn.sorry=false", "-Dweak.says.verify=true"] +[[lean_exe]] +name = "get_cache" +srcDir = "scripts" + [[lean_exe]] name = "check_file_imports" srcDir = "scripts" diff --git a/scripts/get_cache.lean b/scripts/get_cache.lean new file mode 100644 index 0000000000..1797e4604d --- /dev/null +++ b/scripts/get_cache.lean @@ -0,0 +1,90 @@ +/- +Copyright (c) 2026 Physlib community. All rights reserved. +Released under Apache 2.0 license. +-/ +import Lean + +/-! +# Get cache + +Downloads everything needed before a first build, so that `lake build` does +not have to compile from source. + +Fetches both halves: Mathlib's prebuilt files (via Mathlib's own +`lake exe cache get`) and Physlib's own (via Lake's built-in `lake cache`, +backed by the project's R2 bucket -- see `lake-cache.toml` and +`docs/cache-setup.md`). Running one command rather than two is the only +reason the Mathlib step lives here -- pass `--no-mathlib` to skip it. + +Safe to run at any time, and safe to skip: if anything goes wrong -- no +network, an unreachable bucket -- this warns and exits 0, and `lake build` +simply compiles from source as it always did. + +It can be run from the terminal using `lake exe get_cache`. +-/ + +def helpText : String := +"Download everything needed before a first build, so that `lake build` does \ +not have to compile from source. + +Usage: + lake exe get_cache fetch everything needed + lake exe get_cache --no-mathlib skip Mathlib, fetch only Physlib's +" + +/-- `println`, then flush stdout immediately. Without this, messages printed +before spawning a subprocess can sit in a buffer and appear out of order (or +not at all until the child exits) whenever stdout is piped rather than a +terminal -- e.g. `lake exe get_cache | tee log.txt`. -/ +def say (s : String) : IO Unit := do + IO.println s + (← IO.getStdout).flush + +/-- Run a subprocess, inheriting stdout/stderr, with optional extra +environment variables. Returns whether it exited successfully. -/ +def runStreamed (cmd : String) (args : Array String) + (env : Array (String × Option String) := #[]) : IO Bool := do + let child ← IO.Process.spawn { cmd, args, env } + return (← child.wait) == 0 + +/-- The options this program understands. Anything else is rejected up +front, rather than silently ignored and treated as "no flags given" -- +which would otherwise run the full fetch when the user typed a typo'd flag +expecting it to be validated. -/ +def knownFlags : List String := ["--help", "-h", "--no-mathlib"] + +def main (args : List String) : IO UInt32 := do + if let some bad := args.find? (!knownFlags.contains ·) then + say s!"Unknown option: {bad} (try --help)" + return 0 + + if args.contains "--help" || args.contains "-h" then + say helpText + return 0 + + unless ← System.FilePath.pathExists "lakefile.toml" do + say "Run this from the root of the Physlib repository." + return 0 + + let skipMathlib := args.contains "--no-mathlib" + + if !skipMathlib then + say "Fetching Mathlib's prebuilt files ..." + unless ← runStreamed "lake" #["exe", "cache", "get"] do + say " could not fetch Mathlib's cache -- continuing anyway." + say " ('lake build' may then have to compile Mathlib, which is slow.)" + say "" + + say "Fetching Physlib's prebuilt files ..." + let cwd ← IO.currentDir + let configPath := (cwd / "lake-cache.toml").toString + let ok ← runStreamed "lake" #["cache", "get", "--scope=physlib-master"] + #[("LAKE_CONFIG", some configPath)] + if ok then + say "" + say "Done. Now run: lake build" + else + say "" + say "Could not fetch Physlib's cache. This is not fatal -- run 'lake build'" + say "as usual, it will just take longer, compiling from source." + return 0 From 9067d49f31754b0dff797f4382f6f8652233d14c Mon Sep 17 00:00:00 2001 From: Alex-Zughaid <117576511+Alex-Zughaid@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:10:51 +0100 Subject: [PATCH 20/20] docs: cache setup guide and updated install instructions docs/cache-setup.md covers provisioning the R2 bucket end to end, for whoever administers it next. README's install steps now mention lake exe get_cache, with measured timings from a fresh clone: clone + fetch + build in ~4-5 minutes, compiling zero modules, versus 35+ minutes for a from-source CI build. --- README.md | 25 ++++++++++-- docs/cache-setup.md | 96 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 docs/cache-setup.md diff --git a/README.md b/README.md index 208d514a38..c14497dd5f 100644 --- a/README.md +++ b/README.md @@ -83,11 +83,30 @@ or ### Installing Physlib +Physlib publishes its compiled artifacts, so a first-time setup downloads them +instead of compiling 685 files from source. That takes about **five minutes** +rather than the hour-plus a full build costs. + - Clone this repository (or download the repository as a Zip file) - Open a terminal at the top-level in the corresponding directory. -- Run `lake exe cache get`. The command `lake` should have been installed when you installed Lean. -- Run `lake build`. -- Open the directory (not a single file) in Visual Studio Code (or another Lean compatible code editor). +- Run `lake exe get_cache`. This downloads the prebuilt files — both + Mathlib's and Physlib's. (~2-3 minutes; faster afterwards, as Mathlib's are + cached machine-wide and shared with any other Lean project.) It is optional: + if it fails you can carry on, and the next step will just take a lot longer. +- Run `lake build`. With the cache in place this compiles nothing — it only + unpacks what was downloaded. (~1-2 minutes the first time, ~15 seconds after.) +- Open the directory (not a single file) in Visual Studio Code (or another Lean + compatible code editor). + +You do not need an account, a login, or any credential for this — the cache is +public to read. + +Once set up, `lake build` only recompiles files you have actually changed, plus +anything importing them. + +If you want to check the cache is doing its job, `lake build` should report +`Build completed successfully` with no `Built ...` lines. Any `Built` line means +that module was compiled from source rather than restored. At the moment Physlib is divided into two essentially disjoint halves, `Physlib` and `QuantumInfo`. These were two repositories that merged in an effort to create a more cohesive ecosystem for physics diff --git a/docs/cache-setup.md b/docs/cache-setup.md new file mode 100644 index 0000000000..1bbd259b91 --- /dev/null +++ b/docs/cache-setup.md @@ -0,0 +1,96 @@ +# Setting up the Physlib build cache bucket + +Physlib publishes its compiled artifacts so contributors do not have to build +the library from source. Delivery is via Lake's own built-in cache (`lake +cache`), backed by a Cloudflare R2 bucket. It is content-addressed and +per-file, so a contributor on any branch gets hits for whatever they have not +changed, and `lake cache get` can backtrack revisions to find them. + +`scripts/get_cache.lean` (`lake exe get_cache`) is a thin wrapper: it fetches +Mathlib's cache (via Mathlib's own tool) and Physlib's (via `lake cache get` +against this bucket), so a contributor runs one command instead of two. + +Nothing works until the bucket is set up. These are the steps. + +## Why R2 + +For a build cache the dominant cost is **egress** -- every contributor pulls +hundreds of megabytes -- not storage. R2 charges nothing for egress at any +volume, so contributor downloads stay free however far the project grows. +Storage is free to 10GB, which comfortably fits Physlib's artifacts, then +$0.015/GB-month. R2 speaks the S3 API with SigV4, which is exactly what +`lake cache put-staged` uses, so no adapter is needed. + +## 1. Create the bucket — done + +`physlib-cache`, in Western Europe (WEUR). Its S3 API endpoint is already +filled into `lake-cache.toml` as the write path. + +## 2. Allow anonymous reads — outstanding + +Contributors fetch without credentials, so the bucket needs public reads. It +currently has none: no custom domain, and the Public Development URL is +disabled. Pick one, in bucket → Settings: + +- **Custom Domains** (recommended). Cloudflare rate-limits the `r2.dev` + development URL and says it is not intended for production traffic — which + a cache served to every contributor is. A custom domain has no such limit. +- **Public Development URL**. One toggle, and enough to trial the setup. Gives + a `https://pub-.r2.dev` hostname. Expect throttling under real load. + +Note the resulting hostname; it goes into `lake-cache.toml` at step 5. + +Only reads become public. Writes stay behind the key from step 3. + +## 3. Create an API token for CI + +R2 → Manage API tokens → Create token, with **Object Read & Write** limited to +`physlib-cache`. Keep the Access Key ID and Secret Access Key. + +Lake expects them as a single SigV4 credential, colon-separated: + +``` +: +``` + +## 4. Add the GitHub secret + +Repository → Settings → Secrets and variables → Actions → New repository +secret, named `LAKE_CACHE_KEY`, set to the colon-joined pair above. + +The workflows check for this secret and skip the cache steps entirely when it +is absent, so CI keeps working before this point and starts publishing after. + +## 5. Fill in the read endpoint + +Edit `lake-cache.toml` in the repo root and replace `` with the +hostname from step 2 — hostname only, no scheme, no trailing slash. The write +endpoint is already set. + +Filling this in is what makes `lake exe get_cache` actually reach the +bucket -- with a placeholder still in place it points nowhere, the fetch +fails, and the script falls back to its "could not fetch" message rather +than compiling from source silently succeeding. + +## 6. Verify + +Merge to `master` and check the `publish to R2 cache` step ran. Then, from a +clean checkout: + +```bash +lake exe get_cache # both halves in one command +lake build # should be close to a no-op +``` + +## Notes + +- The credential is never committed. Endpoints are public information, which + is why `lake-cache.toml` is in the repo -- so local contributors get the + cache without hand-configuring anything. +- `lake-cache.toml` defines two services deliberately: `physlib-r2` for + anonymous reads and `physlib-r2-upload` for authenticated writes. A + contributor cannot write to the cache even by accident. +- Lake's cache stores generated C alongside oleans; a full upload is roughly + 200-250MB. Still well inside the free tier. +- Costs to watch as the project grows: storage past 10GB, and Class A + (write) operations. Egress, the usual scaling problem, is free on R2.