Imports
/- 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 -/ module public import Physlib.Units.Basic public import Mathlib.Topology.Algebra.Module.Spaces.ContinuousLinearMap

Types which depend on dimensions

In addition to types which carry a dimension, we also have types whose elements depend on a choice of a units. For example a function f : M1 → M2 between two types M1 and M2 which carry dimensions does not itself carry a dimensions, but is dependent on a choice of units.

We define three versions

    UnitDependent M having a function scaleUnit : UnitChoices → UnitChoices → M → M subject to two conditions scaleUnit_trans and scaleUnit_id

    LinearUnitDependent M extends UnitDependent M with additional linearity conditions on scaleUnit.

    ContinuousLinearUnitDependent M extends LinearUnitDependent M with an additional continuity condition on scaleUnit.

@[expose] public section

A type carries the instance UnitDependent M if it depends on a choice of units. This dependence is manifested in scaleUnit u1 u2 which describes how elements of M change under a scaling of units which would take u1 to u2.

This type is used for functions, and propositions etc.

scaleUnit u1 u2 is the map transforming elements of M under a scaling of units which would take the unit u1 to the unit u2. This is not to say that in scaleUnit u1 u2 m that m should be interpreted as being in the units u1, although this is often the case.

class UnitDependent (M : Type) where scaleUnit : UnitChoices UnitChoices M M scaleUnit_trans : u1 u2 u3 m, scaleUnit u2 u3 (scaleUnit u1 u2 m) = scaleUnit u1 u3 m scaleUnit_trans' : u1 u2 u3 m, scaleUnit u1 u2 (scaleUnit u2 u3 m) = scaleUnit u1 u3 m scaleUnit_id : u m, scaleUnit u u m = m

A type M with an instance of UnitDependent M such that scaleUnit u1 u2 is compatible with the MulAction ℝ≥0 M instance on M.

class MulUnitDependent (M : Type) [MulAction ℝ≥0 M] extends UnitDependent M where scaleUnit_mul : u1 u2 (r : ℝ≥0) m, scaleUnit u1 u2 (r m) = r scaleUnit u1 u2 m

A type M with an instance of UnitDependent M such that scaleUnit u1 u2 is compatible with the Module ℝ M instance on M.

class LinearUnitDependent (M : Type) [AddCommMonoid M] [Module M] extends UnitDependent M where scaleUnit_add : u1 u2 m1 m2, scaleUnit u1 u2 (m1 + m2) = scaleUnit u1 u2 m1 + scaleUnit u1 u2 m2 scaleUnit_smul : u1 u2 (r : ) m, scaleUnit u1 u2 (r m) = r scaleUnit u1 u2 m

A type M with an instance of UnitDependent M such that scaleUnit u1 u2 is compatible with the Module ℝ M instance on M, and is continuous.

class ContinuousLinearUnitDependent (M : Type) [AddCommMonoid M] [Module M] [TopologicalSpace M] extends LinearUnitDependent M where scaleUnit_cont : u1 u2, Continuous (scaleUnit u1 u2)

## Basic properties of scaleUnit

All goals completed! 🐙@[simp] lemma UnitDependent.scaleUnit_injective {M : Type} [UnitDependent M] (u1 u2 : UnitChoices) (m1 m2 : M) : scaleUnit u1 u2 m1 = scaleUnit u1 u2 m2 m1 = m2 := fun h => M:Typeinst✝:UnitDependent Mu1:UnitChoicesu2:UnitChoicesm1:Mm2:Mh:scaleUnit u1 u2 m1 = scaleUnit u1 u2 m2m1 = m2 All goals completed! 🐙, congrArg (scaleUnit u1 u2)

### Variations on the map scaleUnit

For an M with an instance of UnitDependent M, scaleUnit u1 u2 as an equivalence.

def UnitDependent.scaleUnitEquiv {M : Type} [UnitDependent M] (u1 u2 : UnitChoices) : M M where toFun m := scaleUnit u1 u2 m invFun m := scaleUnit u2 u1 m right_inv m := M:Typeinst✝:UnitDependent Mu1:UnitChoicesu2:UnitChoicesm:M(fun m => scaleUnit u1 u2 m) ((fun m => scaleUnit u2 u1 m) m) = m All goals completed! 🐙 left_inv m := M:Typeinst✝:UnitDependent Mu1:UnitChoicesu2:UnitChoicesm:M(fun m => scaleUnit u2 u1 m) ((fun m => scaleUnit u1 u2 m) m) = m All goals completed! 🐙

For an M with an instance of LinearUnitDependent M, scaleUnit u1 u2 as a linear map.

def LinearUnitDependent.scaleUnitLinear {M : Type} [AddCommMonoid M] [Module M] [LinearUnitDependent M] (u1 u2 : UnitChoices) : M →ₗ[] M where toFun m := scaleUnit u1 u2 m map_add' m1 m2 := M:Typeinst✝²:AddCommMonoid Minst✝¹:Module Minst✝:LinearUnitDependent Mu1:UnitChoicesu2:UnitChoicesm1:Mm2:MscaleUnit u1 u2 (m1 + m2) = scaleUnit u1 u2 m1 + scaleUnit u1 u2 m2 All goals completed! 🐙 map_smul' r m2 := M:Typeinst✝²:AddCommMonoid Minst✝¹:Module Minst✝:LinearUnitDependent Mu1:UnitChoicesu2:UnitChoicesr:m2:MscaleUnit u1 u2 (r m2) = (RingHom.id ) r scaleUnit u1 u2 m2 All goals completed! 🐙

For an M with an instance of LinearUnitDependent M, scaleUnit u1 u2 as a linear equivalence.

def LinearUnitDependent.scaleUnitLinearEquiv {M : Type} [AddCommMonoid M] [Module M] [LinearUnitDependent M] (u1 u2 : UnitChoices) : M ≃ₗ[] M := LinearEquiv.ofLinear (scaleUnitLinear u1 u2) (scaleUnitLinear u2 u1) (M:Typeinst✝²:AddCommMonoid Minst✝¹:Module Minst✝:LinearUnitDependent Mu1:UnitChoicesu2:UnitChoicesscaleUnitLinear u1 u2 ∘ₗ scaleUnitLinear u2 u1 = LinearMap.id M:Typeinst✝²:AddCommMonoid Minst✝¹:Module Minst✝:LinearUnitDependent Mu1:UnitChoicesu2:UnitChoicesu:M(scaleUnitLinear u1 u2 ∘ₗ scaleUnitLinear u2 u1) u = LinearMap.id u; All goals completed! 🐙) (M:Typeinst✝²:AddCommMonoid Minst✝¹:Module Minst✝:LinearUnitDependent Mu1:UnitChoicesu2:UnitChoicesscaleUnitLinear u2 u1 ∘ₗ scaleUnitLinear u1 u2 = LinearMap.id M:Typeinst✝²:AddCommMonoid Minst✝¹:Module Minst✝:LinearUnitDependent Mu1:UnitChoicesu2:UnitChoicesu:M(scaleUnitLinear u2 u1 ∘ₗ scaleUnitLinear u1 u2) u = LinearMap.id u; All goals completed! 🐙)

For an M with an instance of ContinuousLinearUnitDependent M, scaleUnit u1 u2 as a continuous linear map.

def ContinuousLinearUnitDependent.scaleUnitContLinear {M : Type} [AddCommMonoid M] [Module M] [TopologicalSpace M] [ContinuousLinearUnitDependent M] (u1 u2 : UnitChoices) : M →L[] M where toLinearMap := LinearUnitDependent.scaleUnitLinear u1 u2 cont := ContinuousLinearUnitDependent.scaleUnit_cont u1 u2

For an M with an instance of ContinuousLinearUnitDependent M, scaleUnit u1 u2 as a continuous linear equivalence.

def ContinuousLinearUnitDependent.scaleUnitContLinearEquiv {M : Type} [AddCommMonoid M] [Module M] [TopologicalSpace M] [ContinuousLinearUnitDependent M] (u1 u2 : UnitChoices) : M ≃L[] M := ContinuousLinearEquiv.mk (LinearUnitDependent.scaleUnitLinearEquiv u1 u2) (ContinuousLinearUnitDependent.scaleUnit_cont u1 u2) (ContinuousLinearUnitDependent.scaleUnit_cont u2 u1)
@[simp] lemma ContinuousLinearUnitDependent.scaleUnitContLinearEquiv_apply {M : Type} [AddCommGroup M] [Module M] [TopologicalSpace M] [ContinuousLinearUnitDependent M] (u1 u2 : UnitChoices) (m : M) : (ContinuousLinearUnitDependent.scaleUnitContLinearEquiv u1 u2) m = scaleUnit u1 u2 m := rfl@[simp] lemma ContinuousLinearUnitDependent.scaleUnitContLinearEquiv_symm_apply {M : Type} [AddCommGroup M] [Module M] [TopologicalSpace M] [ContinuousLinearUnitDependent M] (u1 u2 : UnitChoices) (m : M) : (ContinuousLinearUnitDependent.scaleUnitContLinearEquiv u1 u2).symm m = scaleUnit u2 u1 m := rfl

Instances of the type classes

We construct instance of the UnitDependent, LinearUnitDependent and ContinuousLinearUnitDependent type classes based on CarriesDimension and functions.

@[simp] lemma UnitChoices.scaleUnit_apply_fst (u1 u2 : UnitChoices) : (scaleUnit u1 u2 u1) = u2 := u1:UnitChoicesu2:UnitChoicesscaleUnit u1 u2 u1 = u2 u1:UnitChoicesu2:UnitChoices(scaleUnit u1 u2 u1).length = u2.lengthu1:UnitChoicesu2:UnitChoices(scaleUnit u1 u2 u1).time = u2.timeu1:UnitChoicesu2:UnitChoices(scaleUnit u1 u2 u1).mass = u2.massu1:UnitChoicesu2:UnitChoices(scaleUnit u1 u2 u1).charge = u2.chargeu1:UnitChoicesu2:UnitChoices(scaleUnit u1 u2 u1).temperature = u2.temperature u1:UnitChoicesu2:UnitChoices(scaleUnit u1 u2 u1).length = u2.lengthu1:UnitChoicesu2:UnitChoices(scaleUnit u1 u2 u1).time = u2.timeu1:UnitChoicesu2:UnitChoices(scaleUnit u1 u2 u1).mass = u2.massu1:UnitChoicesu2:UnitChoices(scaleUnit u1 u2 u1).charge = u2.chargeu1:UnitChoicesu2:UnitChoices(scaleUnit u1 u2 u1).temperature = u2.temperature All goals completed! 🐙@[simp] lemma UnitChoices.dimScale_scaleUnit {u1 u2 u : UnitChoices} (d : Dimension LTMCTDimensionBase) : u.dimScale (scaleUnit u1 u2 u) d = u1.dimScale u2 d := u1:UnitChoicesu2:UnitChoicesu:UnitChoicesd:Dimension LTMCTDimensionBase(u.dimScale (scaleUnit u1 u2 u)) d = (u1.dimScale u2) d u1:UnitChoicesu2:UnitChoicesu:UnitChoicesd:Dimension LTMCTDimensionBase(↑(u2.length / u1.length))⁻¹, ^ d.length * (↑(u2.time / u1.time))⁻¹, ^ d.time * (↑(u2.mass / u1.mass))⁻¹, ^ d.mass * (↑(u2.charge / u1.charge))⁻¹, ^ d.charge * (↑(u2.temperature / u1.temperature))⁻¹, ^ d.temperature = (u1.length / u2.length) ^ d.length * (u1.time / u2.time) ^ d.time * (u1.mass / u2.mass) ^ d.mass * (u1.charge / u2.charge) ^ d.charge * (u1.temperature / u2.temperature) ^ d.temperature All goals completed! 🐙All goals completed! 🐙lemma HasDim.scaleUnit_apply {M : Type} [CarriesDimension M] (u1 u2 : UnitChoices) (m : M) : scaleUnit u1 u2 m = (u1.dimScale u2 (dim M)) m := toDimensionful_apply_apply u1 u2 m

Functions

@[simp] lemma UnitDependent.scaleUnit_apply_fun_right {M1 M2 : Type} [UnitDependent M2] (u1 u2 : UnitChoices) (f : M1 M2) (m1 : M1) : scaleUnit u1 u2 f m1 = scaleUnit u1 u2 (f m1) := rfl@[simp] lemma UnitDependent.scaleUnit_apply_fun_left {M1 M2 : Type} [UnitDependent M1] (u1 u2 : UnitChoices) (f : M1 M2) (m1 : M1) : scaleUnit u1 u2 f m1 = f (scaleUnit u2 u1 m1) := rfl@[simp] lemma UnitDependent.scaleUnit_apply_fun {M1 M2 : Type} [UnitDependent M1] [UnitDependent M2] (u1 u2 : UnitChoices) (f : M1 M2) (m1 : M1) : scaleUnit u1 u2 f m1 = scaleUnit u1 u2 (f (scaleUnit u2 u1 m1)) := rfl@[simp] lemma ContinuousLinearUnitDependent.scaleUnit_apply_fun {M1 M2 : Type} [AddCommGroup M1] [Module M1] [TopologicalSpace M1] [ContinuousLinearUnitDependent M1] [AddCommGroup M2] [Module M2] [TopologicalSpace M2] [ContinuousConstSMul M2] [IsTopologicalAddGroup M2] [ContinuousLinearUnitDependent M2] (u1 u2 : UnitChoices) (f : M1 →L[] M2) (m1 : M1) : scaleUnit u1 u2 f m1 = scaleUnit u1 u2 (f (scaleUnit u2 u1 m1)) := rfl

isDimensionallyCorrect

A term of type M carrying an instance of UnitDependent M is said to be dimensionally correct if under a change of units it remains the same.

More colloquially, something is dimensionally correct if it (e.g. it's value or its truth for a proposition) does not depend on the units it is written in.

For the case of m : M with CarriesDimension M then IsDimensionallyCorrect m corresponds to the statement that m does not depend on units, e.g. is zero or the dimension of M is zero.

def IsDimensionallyCorrect {M : Type} [UnitDependent M] (m : M) : Prop := u1 u2 : UnitChoices, scaleUnit u1 u2 m = m
lemma isDimensionallyCorrect_iff {M : Type} [UnitDependent M] (m : M) : IsDimensionallyCorrect m u1 u2 : UnitChoices, scaleUnit u1 u2 m = m := M:Typeinst✝:UnitDependent Mm:MIsDimensionallyCorrect m (u1 u2 : UnitChoices), scaleUnit u1 u2 m = m All goals completed! 🐙@[simp] lemma isDimensionallyCorrect_fun_iff {M1 M2 : Type} [UnitDependent M1] [UnitDependent M2] {f : M1 M2} : IsDimensionallyCorrect f u1 u2 : UnitChoices, m, scaleUnit u1 u2 (f (scaleUnit u2 u1 m)) = f m := M1:TypeM2:Typeinst✝¹:UnitDependent M1inst✝:UnitDependent M2f:M1 M2IsDimensionallyCorrect f (u1 u2 : UnitChoices) (m : M1), scaleUnit u1 u2 (f (scaleUnit u2 u1 m)) = f m All goals completed! 🐙@[simp] lemma isDimensionallyCorrect_fun_left {M1 M2 : Type} [UnitDependent M1] {f : M1 M2} : IsDimensionallyCorrect f u1 u2 : UnitChoices, m, (f (scaleUnit u2 u1 m)) = f m := M1:TypeM2:Typeinst✝:UnitDependent M1f:M1 M2IsDimensionallyCorrect f (u1 u2 : UnitChoices) (m : M1), f (scaleUnit u2 u1 m) = f m All goals completed! 🐙@[simp] lemma isDimensionallyCorrect_fun_right {M1 M2 : Type} [UnitDependent M2] {f : M1 M2} : IsDimensionallyCorrect f u1 u2 : UnitChoices, m, scaleUnit u1 u2 (f m) = f m := M1:TypeM2:Typeinst✝:UnitDependent M2f:M1 M2IsDimensionallyCorrect f (u1 u2 : UnitChoices) (m : M1), scaleUnit u1 u2 (f m) = f m All goals completed! 🐙

Some type classes to help track dimensions

The multiplication of an element of M1 with an element of M2 to get an element of M3 in such a way that dimensions are preserved.

class DMul (M1 M2 M3 : Type) [CarriesDimension M1] [CarriesDimension M2] [CarriesDimension M3] extends HMul M1 M2 M3 where mul_dim : (m1 : Dimensionful M1) (m2 : Dimensionful M2), HasDimension (fun u => hMul (m1.1 u) (m2.1 u))
@[simp] lemma DMul.hMul_scaleUnit {M1 M2 M3 : Type} [CarriesDimension M1] [CarriesDimension M2] [CarriesDimension M3] [DMul M1 M2 M3] (m1 : M1) (m2 : M2) (u1 u2 : UnitChoices) : (scaleUnit u1 u2 m1) * (scaleUnit u1 u2 m2) = scaleUnit u1 u2 (m1 * m2) := M1:TypeM2:TypeM3:Typeinst✝³:CarriesDimension M1inst✝²:CarriesDimension M2inst✝¹:CarriesDimension M3inst✝:DMul M1 M2 M3m1:M1m2:M2u1:UnitChoicesu2:UnitChoicesscaleUnit u1 u2 m1 * scaleUnit u1 u2 m2 = scaleUnit u1 u2 (m1 * m2) All goals completed! 🐙

Dim Subtype

Given a type M that depends on units, e.g. the function type M1 → M2 between two types carrying a dimension, the subtype of M which scales according to the dimension d.

def DimSet (M : Type) [MulAction ℝ≥0 M] [MulUnitDependent M] (d : Dimension LTMCTDimensionBase) : Set M := {m : M | u1 u2, scaleUnit u1 u2 m = (UnitChoices.dimScale u1 u2 d) m}
All goals completed! 🐙instance (M : Type) [MulAction ℝ≥0 M] [MulUnitDependent M] (d : Dimension LTMCTDimensionBase) : CarriesDimension (DimSet M d) where d := d@[simp] lemma scaleUnit_dimSet_val {M : Type} [MulAction ℝ≥0 M] [MulUnitDependent M] (d : Dimension LTMCTDimensionBase) (m : DimSet M d) (u1 u2 : UnitChoices) : (scaleUnit u1 u2 m).1 = scaleUnit u1 u2 m.1 := (m.2 u1 u2).symmlemma DimSet.mem_iff {M : Type} [MulAction ℝ≥0 M] [MulUnitDependent M] (d : Dimension LTMCTDimensionBase) (m : M) : m DimSet M d u1 u2, scaleUnit u1 u2 m = (UnitChoices.dimScale u1 u2 d) m := M:Typeinst✝¹:MulAction ℝ≥0 Minst✝:MulUnitDependent Md:Dimension LTMCTDimensionBasem:Mm DimSet M d (u1 u2 : UnitChoices), scaleUnit u1 u2 m = (u1.dimScale u2) d m All goals completed! 🐙