import LeanUnits.Framework.Dimensions.Basic import LeanUnits.Framework.Conversions.Basic import LeanUnits.Framework.Utils import Mathlib.Data.DFinsupp.Defs import Mathlib.Data.DFinsupp.BigOperators import Batteries.Data.Rat import Mathlib.Data.Rat.Defs namespace Units /-- Choosing a metric involves choosing - a power factor (it's one for base and derived units) - an affine conversion (to convert between units of the same dimension) - a dimension (to ensure dimensional correctness) Note that all these 3 choices form a AddCommGroup, so we can combine them easily. -/ abbrev UnitChoice := ℚ × Conversion × Dimension /-- A unit is a product of base units raised to rational powers. For example, the unit of force in the SI system is `kg•m/s²` can be represented as : - `force₁ = {"kg" ↦ (1,0,Mass), "m" ↦ (1,0,Length), "s" ↦ (-2,0,Time⁻²)}` or when using derived units: - `force₂={"N" ↦ (1,0,Mass•Length•Time⁻²)}` Converting between these two representations is possible because their dimensions are the same under product: - `Π 𝒟(force₁) = Mass•Length•Time⁻² = Π 𝒟(force₂)` -/ structure Unit where _impl : DFinsupp (fun _ : String => UnitChoice) deriving DecidableEq, BEq namespace Unit instance instEquiv : Unit ≃ (DFinsupp (fun _ : String => UnitChoice)) where toFun := Unit._impl invFun := Unit.mk instance instAddCommGroup : AddCommGroup Unit := Unit.instEquiv.addCommGroup -- implement convenient syntax for units, because addition is confusing @[simp] instance : One Unit where one := 0 @[simp] instance : Mul Unit where mul u1 u2 := u1 + u2 @[simp] instance : Inv Unit where inv u := -u @[simp] instance : Div Unit where div u1 u2 := u1 - u2 @[simp] instance : Pow Unit ℕ where pow u q := q • u @[simp] instance : Pow Unit ℤ where pow u n := n • u def dimension (u : Unit) : Dimension := u._impl.sum (fun _ qd => qd.2.2) def conversion (u : Unit) : Conversion := u._impl.sum (fun _ qd => qd.2.1) instance : HasDimension Unit where dimension := Unit.dimension instance : HasConversion Unit where conversion := Unit.conversion /-- Two units are considered equal if they have the same dimension and conversion, regardless of their representation. For example, `N` and `kg•m/s²` are considered equivalent -/ @[simp] instance instSetoidUnit : Setoid Unit where r:= fun u₁ u₂ => u₁.dimension = u₂.dimension ∧ u₁.conversion = u₂.conversion iseqv := ⟨ fun _u => ⟨rfl, rfl⟩, fun {_u₁ _u₂} ⟨h1, h2⟩ => ⟨h1.symm, h2.symm⟩, fun {_u₁ _u₂ _u₃} ⟨h1, h2⟩ ⟨h3, h4⟩ => ⟨h1.trans h3, h2.trans h4⟩ ⟩ -- decidable equivalence of units instance instDecidableEquivUnit (u1 u2 : Unit) : Decidable (u1 ≈ u2) := by dsimp [HasEquiv.Equiv, instSetoidUnit] infer_instance /-- Define a base unit with a given name and dimension. Example: - `meter = defineUnit "m" Dimension.Length` -/ def defineUnit (s : String) (d : Dimension) : Unit := ⟨DFinsupp.single s (1,0,d)⟩ /-- Define a derived unit with a given name, unit and optional conversion. Example: - `newton = defineDerivedUnit "N" (kilogram * meter / second^2)` - `celsius = defineDerivedUnit "°C" kelvin (Conversion.translate (27315/100))` -/ def defineDerivedUnit (s : String) (u : Unit) (conv : Conversion := 0) : Unit := ⟨DFinsupp.single s (1,conv.div u.conversion,u.dimension)⟩ section Repr open Lean Parser Term unsafe instance : Repr Unit where reprPrec f _ := let vals := f._impl.support'.unquot.val.map (fun i => (i,(f._impl i))) |>.unquot |>.filter (·.2 != 0) |>.dedup |>.mergeSort (fun a b => a.1 < b.1) if vals.length = 0 then "∅" else let parts : List String := vals.map (fun a => formatExp a.1 a.2.1.1) f!"{String.intercalate "•" parts}" end Repr end Unit end Units