Types

Quantities

FlexUnits.jl has two types of quantity values. FlexQuant, which can contain any object as a value, and Quantity which subtypes to Number (following the convention of most of Julia's unit packages). For convenience, they are often referred to together as QuantUnion. Mathematical operations on a QuantUnion with a Unit will convert to a dimensional quantity (SI by default) having an AbstractDimension instead of a AbstractUnit.

FlexUnits.QuantityType
struct Quantity{T<:Number, U<:AbstractUnitLike} <: Number
    value :: T
    unit  :: U
end

Numeric quantity type (that sutypes to number) with fields value and unit

source
FlexUnits.FlexQuantType
struct FlexQuant{T<:Any, U<:AbstractUnitLike}
    value :: T
    unit  :: U
end

Generic quantity type (that can hold any value) with fields value and unit

source
FlexUnits.QuantUnionType
QuantUnion{T<:Any,U<:AbstractUnitLike}

Convenience union that allows Number and Non-Number types to be considered together

source

Dimensions

Dimensions are the core object used to define units, and Quantity values tend to have some form of AbstractDimension in its unit field after calculations due to computational simplicity.

FlexUnits.AbstractDimensionsType
AbstractDimensions

A class that represents a coherent units system (i.e. dimensions and a base unit for each dimension) By default, its children are Dimensions (SI units), and NoDims, but users can build their own versions (even based on imperial equivalents, or where angles are a dimension)

source
FlexUnits.DimensionsType
Dimensions{P}

A dimensional structure with fields corresponding to fundamental SI units: m, kg, s, A, K, cd, mol

source
FlexUnits.StaticDimsType
StaticDims{D} <: AbstractDimLike

Static dimensions where the D` is the dimension value. This improves performance when dimensions are statically inferrable.

source

Units

Units contain an AbstractDimensions and an AbstractUnitTransform which represents a conversion formula to convert the Quantity to its pure dimensional form. Typically, the unit transform is called before a calculation to convert the quantity to dimensional form, and uconvert applies an invers of that transform to convert the result back to the desired unit. Because dimensions have no transforms, it is most efficient to perform calculations directly on dimensional quantities.

FlexUnits.AbstractUnitsType
AbstractUnits

All units in FlexDims contain two entities: (1) AbstractDimLike (anything that can be interpreted as a dimension) (2) AbstractTransform (contains the formula to convert unit value to dimensions)

source
FlexUnits.UnitsType
@kwdef struct Units{D<:AbstractDimLike, T<:AbstractUnitTransform} <: AbstractUnits{D, T}
    dims   :: D
    tobase :: T
    symbol :: Symbol = DEFAULT_USYMBOL
end

A dynamic unit object that contains dimensions (dims) and its conversion formula to said dimensions (tobase). The conversion formula determines what kind of unit is referred to. An AffineTransform implies affine units, a NoTransform implies dimensions. Units with dynamic dimensions can generated through the @ud_str macro, while units with static dimensions can be generated throiugh the @u_str macro.

Constructors

Units{D}(units, tobase::T, symbol=DEFAULT_USYMBOL) where {D,T<:AbstractUnitTransform} 
Units(dims::D, tobase::AbstractUnitTransform=NoTransform(), symbol=DEFAULT_USYMBOL) where D<:AbstractDimensions 
Units(units::D, tobase::AbstractUnitTransform, symbol=DEFAULT_USYMBOL) where D<:AbstractUnits
julia> 1*(5ud"°C") #Operations on units eagerly convert to dimensions
278.15 K

julia> 1*(5du"°C") |> ud"°C" #Converts operation results back to Celsius
5.0 °C

julia> (5ud"°C" + 2ud"°C") |> ud"°C" #Operation adds values in Kelvin, results converted back to Celsius
280.15 °C

julia> (ustrip(5ud"°C") + ustrip(2ud"°C"))*u"°C" #Strips, adds raw quantity values, converts raw number to Celsius
7 °C
source

Logarithmic Quantities and Units

Logarithmic quantities have different algebraic rules around units. Multiplication raises units to a power, addition multiplies units, and new operators ⊕ and ⊖ verify units (much like addition and subtraction for linear quantities). The LogScale is a multi-purpose callable object that can create logarithmic units (when called on linear units), and can change the logarithmic scale of a LogQuant (when called on a LogQuant). For example dB(q::LogQuant) will convert q to decibels.

FlexUnits.LogQuantType
struct LogQuant{T<:Number, U<:AbstractUnitLike} <: Number 
    value :: T 
    unit  :: U 
end

Numeric quantity type (that subtypes to number) representing the lograrithm of a Quantity. This changes arithmeic operations (+-*/) according to logarithmic algebra.

source
FlexUnits.LogScaleType
struct LogScale{T<:Real}
    scale :: T
    base :: T
    symbol :: Symbol
end

A callable object used to apply a scale to a logarithmic unit. For example,

dB = LogScale(scale=0.1, base=10, symbol=:dB)

After defining it, this object can also be called on a LogQuant to change the logarithmic scale

julia> dB(log(10u"kPa"))
40.0 dB(kg/(m s²))

It can also be used to create logarithmic units that you can convert to

julia> 10u"kPa" |> dB(u"Pa")
39.99999999999999 dB(Pa)
source

Unit Transforms

One unique feature to the FlexUnits design is the AbstractUnitTransform object. This is a callable object that contains a conversion formula to convert the unit into its dimensional form. The default transforms are NoTransform (a property of all dimensions) and AffineTransform which can deal with all common linear units. This design could potentially support other transforms like LogTransform for logarithmic units like dB and pH in the future.

FlexUnits.AbstractUnitTransformType
AbstractUnitTransform

An abstract object representing a unit conversion formula. Any object that subtypes this is made callable.

# Callable form 
utrans = uconvert(u"°C", u"°F")
utrans(0.0)
31.999999999999986

# Shorthand callable form (syntactic sugar)
(u"°C" |> u"°F")(0.0)
31.999999999999986
source
FlexUnits.NoTransformType

NoTransform object, the default transform returned by tobase(x::AbstractDimensionLike). Calling it results in an identity.

t = NoTransform()
t("anything")
"anything"
source
FlexUnits.AffineTransformType
@kwdef struct AffineTransform{T<:Real} <: AbstractUnitTransform
    scale  :: T = 1.0
    offset :: T = 0.0
end

A type representing an affine transfomration formula that can be used to convert values from one affine unit to another. This object is callable.

Constructors

AffineTransform(scale::Real, offset::Real)
AffineTransform(; scale, offset)
source

QuantFieldArrray

Objects are a subtype of FieldArray, which can store multiple values with different static dimensions in a type-stable manner. This is done by returning only the magnitude when indexed by number, but the quantity (both magnitude and dimensions) when indexed by field name. This allows for easy cross-conversion between the agnostic SArray and QuantFieldArray types allowing for dimension-checking in dimension-aware engineering calculations, but forgoes this in linear algebra operations allowing for unit checking only when needed at zero cost. These types are not exported by default and need to be explicitly imported.

FlexUnits.QuantFieldArrayType
abstract type QuantFieldArray{N,T,D} <: FieldArray{N,T,D} end

Inheriting from this object will make it easier to define your own rank-D tensor types with known units (only supports fundamental units).

@kwdef struct Stiffness{T<:Real} <: QuantFieldArray{Tuple{2,2,2}, T, 3}
    xxx::Quantity{T, D"N/m"}
    yxx::Quantity{T, D"N/m"}
    xyx::Quantity{T, D"N/m"}
    yyx::Quantity{T, D"N/m"}
    xxy::Quantity{T, D"N/m"}
    yxy::Quantity{T, D"N/m"}
    xyy::Quantity{T, D"N/m"}
    yyy::Quantity{T, D"N/m"}
end

Calling getproperty(qa::QuantFieldArray, fn) will produce a Quantity,

stiffness = Stiffness(0.01u"lbf/inch" .* ones(2,2,2))
stiffness.xxx
1.7512677165354331 kg/s²

while calling getindex(qa::QuantFieldArray, ind) will produce a pure numerical scalar in the coherent base units (fundamental SI units by default).

stiffness[1]
1.7512677165354331

This means that linear algebra operations will only "see" a numerical array but engineering formulas that index by field will have (coherent) units attached to them ensuring unit correctness for such formulas. This results in near zero-overhead unit verification cost in applications like ODE solving

source
FlexUnits.QuantFieldMatrixType
abstract type QuantFieldMatrix{N1, N2, T} <: QuantFieldArray{Tuple{N1, N2}, T, 2} end

Inheriting from this object will make it easier to define your own static matrices with known units (only supports fundamental units).

@kwdef struct Stiffness{T<:Real} <: QuantFieldMatrix{2, 2, T}
    xx::Quantity{T, D"N/m"}
    yx::Quantity{T, D"N/m"}
    xy::Quantity{T, D"N/m"}
    yy::Quantity{T, D"N/m"}
end

Calling getproperty(qa::QuantFieldMatrix, fn) will produce a Quantity,

stiffness = Stiffness{Float64}(0.01u"lbf/inch" .* ones(2,2))
stiffness.xx
1.7512677165354331 kg/s²

while calling getindex(qa::QuantFieldMatrix, ind) will produce a pure numerical scalar in the coherent base units (fundamental SI units by default).

stiffness[1]
1.7512677165354331

This means that linear algebra operations will only "see" a numerical matrix but engineering formulas that index by field will have (coherent) units attached to them ensuring unit correctness for such formulas. This results in near zero-overhead unit verification cost in applications like ODE solving

source
FlexUnits.QuantFieldVectorType
abstract type QuantFieldVector{N, T} <: QuantFieldArray{Tuple{N}, T, 1} end

Inheriting from this object will make it easier to define your own static vectors with known units (only supports fundamental units).

@kwdef struct ThermoState{NT<:Real} <: QuantFieldVector{3, NT}
    T::Quantity{NT, D"K"}
    P::Quantity{NT, D"Pa"}
    V::Quantity{NT, D"m^3/mol"}
end

Calling getproperty(qa::QuantFieldMatrix, fn) will produce a Quantity,

state = ThermoState{Float64}(T=5u"°C", P=101.5u"kPa", V=1.8u"m^3/mol")
state.T
278.15 K

while calling getindex(qa::QuantFieldMatrix, ind) will produce a pure numerical scalar in the coherent base units (fundamental SI units by default).

state[1]
278.15

This means that linear algebra operations will only "see" a numerical vector but engineering formulas that index by field will have (coherent) units attached to them ensuring unit correctness for such formulas. This results in near zero-overhead unit verification cost in applications like ODE solving

source
FlexUnits.DimsModType
struct DimsMod{SD<:StaticDims, N, T, D, A<:QuantFieldArray} <: QuantFieldArray{N, T, D}
    parent :: A 
end

A structure that modifies the static dimensiosn of a QuantFieldArray by a constant factor "D". Useful for operations that modify a QuantFieldArray with a constant dimension (for example, like taking time derivatives). The inner parent of the structure can be captured using ustrip(dm::DimsMod).

source

Linear Algebra

Linear algebra functionality is achieved by observing that any matrix of quantities that supports multiplication is a special kind of quantity matrix that functions as a linear mapping. Such matrices have a special structure for units. These special unit structures are unit mappings that contain an input vector of units, and an output vector of units. Accelerated linear algebra operations are achieved by keeping numerical matrices and unit mappings separate in a LinmapQuant and performing the linear algebra and unit inference separately (not unlike how a Quantity operates).

Unit Maps

FlexUnits.UnitMapType
struct UnitMap{UI, UO, TI<:ScalarOrVec{UI}, TO<:ScalarOrVec{UO}} <: AbstractUnitMap{UI,UO}
    u_in  :: TI
    u_out :: TO
end

Used to represent a unit transformation from input units 'uin' to outpout units 'uout'. Often applied to nonlinear functions.

Constructors

UnitMap(u_in::ScalarOrVec{<:AbstractUnitLike}, u_out::ScalarOrVec{<:AbstractUnitLike}) where U<:AbstractUnitLike
source
FlexUnits.DimsMapType
struct struct DimsMap{D<:AbstractDimLike, TI<:AbstractDimVector, TO<:AbstractDimVector} <: AbstractDimsMap{D}
    u_fac :: D
    u_in  :: TI
    u_out :: TO
end

Used to represent a unit transformation from input dimensions 'uin' to outpout dimensions 'uout'. This is like a unit map but focuses on dimensions and has matrix-like behaviour since dimensions support linear algebra, but generic units may not (affine units, logarithmic units etc).

WARNING: The DimsMap constructor on dimensions expects uin and uout to be scaled so that the first element is dimensionless. To prevent excessive allocations, uin and uout may be mutated in-place. If mutating arguments is undesirable, supply immutable arguments or copies; otherwise, ensure that uin and uout have dimensionless values in their first element.

Constructors

DimsMap(u_fac::U, u_in::TI, u_out::TO) where {U<:AbstractUnitLike, TI<:AbstractVector{<:AbstractUnitLike}, TO<:AbstractVector{<:AbstractUnitLike}}
DimsMap(u_fac::Nothing, u_in::TI, u_out::TO) where {TI<:AbstractVector{<:AbstractUnitLike}, TO<:AbstractVector{<:AbstractUnitLike}}
DimsMap(md::AbstractMatrix{<:AbstractDimLike})
DimsMap(mq::AbstractMatrix{<:QuantUnion})
DimsMap(d::AbstractDimsMap)
source

Objects with Unit Maps

FlexUnits.LinmapQuantType
struct LinmapQuant{T, D<:AbstractDimensions, M<:AbstractMatrix{T}, U<:UnitMaps{D}} <: AbstractMatrix{Quantity{T,D}}
    values :: M
    units :: U
end

A linear mapping of quantities. A special kind of matrix that is intended to be used for multiplying vectors of quantities; such matrices must be dimensionally consistent and can be represented by a UnitMap. These constraints lead to much faster unit inference and a smaller memory footprint (O(M+N) instead of O(MNN2) in the case of multiplication).

Constructors

LinmapQuant(m::AbstractMatrix{T}, u::UnitMap) where T
LinmapQuant(m::SMatrix{Nr,Nc,T}, u::UnitMap) where {T, Nr, Nc}
LinmapQuant(m::QuantArrayVals, d::QuantArrayDims)
LinmapQuant(m::AbstractMatrix)
LinmapQuant(m::LinmapQuant)
source
FlexUnits.FunctionQuantType
struct FunctionQuant{F, U<:AbstractUnitMap}
    func  :: F
    units :: U
end

A generic mapping with units. Useful for applying units to unitless functions that assume units for inputs/outputs.

source
FlexUnits.FactorQuantType
struct FactorQuant{T, D<:AbstractDimensions, F<:Factorization{T}, U<:AbstractUnitMap{D}}
    factor :: F
    dims  :: U 
end

A factored linear mapping. A subclass of Factorizations with a unit mapping attached. Calling getproperty is re-routed to the original factor, with the appropriate units calcualted from the mapping.

source