LessUnitfulModule

linux-macos-windows Aqua QA

LessUnitful

Small package which provides convenience tools to access quantities based on Unitful.jl and PhysicalConstants.jl in an "unitless" way – as floating point numbers representing the numerical value of a quantity expressed in preferred units (SI base units by default). This appears to be useful in projects using code which cannot easily made unit-aware, e.g. due to the use of sparse linear algebra.

Breaking changes in v1.1

  • Unitful.@u_str is not anymore re-exported. Instead, using Unitful should be used.
  • The functor method (::Unitful.FreeUnits)(x::Real) is now exported by the submodule LessUnitful.MoreUnitful. So if something like x=1|>u"cm" is required (which should give 100cm because 1 is assumed to be a value in the SI Basic units in this method), one needs LessUnitful.MoreUnitful. Due to the type piracy behind this, using this in packages should be avoided.
  • Calculation of physical constants like ph"N_A*e" has been removed as this depended on undocumented internals of Unitful.jl. Just replace this by ph"N_A"*ph"e".
source

Why this package ?

We demonstrate this with an example. Assume we have a nice function which calculates with unitful values:

julia> using Unitful

julia> unitful_lsum(a::Unitful.Length, b::Unitful.Length) = a+b;

julia> unitful_lsum(10.0u"cm",1.0u"m")
1.1 m

Now we assume that there is a faster way to calculate the same sum with the caveat that the implementation does not support unitful values, e.g. because it uses a binary package written in C. (here, we just use a mock example, in reality, think about sparse linear algebra)

julia> fast_lsum(a::Real, b::Real)=a+b;

If we want to use this method in a faster implementation of unitful_area, we might try to use Unitful.ustrip to strip the units and to attach them back somehow afterwards. However, before stripping the arguments need to be brought to a common unit. Canonically, this is the preferred unit of the corresponding dimension provided by Unitful (SI base unit by default).

julia> function fast_unitful_lsum(a::Unitful.Length, b::Unitful.Length)
           a_stripped=a |> upreferred |> ustrip
           b_stripped=b |> upreferred |> ustrip
           fast_lsum(a_stripped, b_stripped)* unit(upreferred(a))
       end;

julia> fast_unitful_lsum(10.0u"cm",1.0u"m")
1.1 m

LessUnitful.jl provides

julia> using LessUnitful, LessUnitful.MoreUnitful

julia> function fast_lessunitful_lsum(a::Unitful.Length, b::Unitful.Length)
           a_stripped=a |> unitfactor
           b_stripped=b |> unitfactor
           fast_lsum(a_stripped, b_stripped) |> u"m"
       end;

julia> fast_lessunitful_lsum(10.0u"cm",1.0u"m")
1.1 m

Also it helps to support a complete "less unitful" workflow:

julia> fast_lsum(10.0ufac"cm",1.0ufac"m")
1.1

It is also possible to attach arbitrary units back in a way consistent to the preferred units:

julia> fast_lsum(10.0ufac"cm",1.0ufac"m") |> u"cm"
110.00000000000001 cm

Notations

The package provides tools to access Unitful.jl to define floating point constants for units like cm, kPa, mV etc. called unit factors. These unit factors relate units to their corresponding products of powers of unitful preferred units in the sense of Unitful.jl.

By default the unitful preferred units are synonymous with the SI base units.

Example: Unit: kN $\to$ representation in powers of SI base units: $1000\cdot kg\cdot m\cdot s^{-2}$ $\to$ unit factor: 1000.0

julia> using LessUnitful

julia> @unitfactors kN
1000.0

The unit factor of a quantity like 5cm, 10kPa, 3mV is its numerical value after conversion to products of powers of unitful preferred units.

Example: Quantity: 3kN $\to$ representation in powers of SI base units: $3000\cdot kg\cdot m\cdot s^{-2}$ $\to$ unit factor: 3000.0

julia> using LessUnitful

julia> ufac"3kN"
3000.0

The unitfactor represents the numerical value of a unit/quatity expressed in preferred units:

julia> using LessUnitful, Unitful

julia> unitfactor(u"cm")==Unitful.ustrip(Unitful.upreferred(u"1.0cm"))
true

Reciprocally, one can make a number "unitful", assuming it corresponds to one of the preferred units. Assume x represents an unit factor of a quantity with respect to the corresponding products of powers of unitful preferred units. Create this quantity and convert it to unit:

julia> using LessUnitful, Unitful

julia> unitful(0.03,u"cm")==u"cm"(Unitful.float(0.03*Unitful.upreferred(u"cm")))
true

Providing these unit factors allows a "unitless" workflow with physical data which is characterized by the following aspects:

  • All calculations are done in numerical values corresponding to unitful preferred units (SI base units by default)
  • Data input can be performed in at least two different ways:
    • Declare @unitfactors cm and enter e.g. length=10*cm
    • Enter "unitful" length=10u"cm" and actually provide unitfactor(length) to the "unitless" code
  • Data output can go as follows - assume result p is a pressure:
    • Declare @unitfactors kPa and do println("p= ",p/kPa,"kPa")
    • Alternatively, do println(unitful(p,u"kPa")), or (with LessUnitful.MoreUnitful): println(p|> u"kPa")

Obtaining unit factors

LessUnitful.unitfactorFunction
unitfactor(x)

Calculate the unit factor of a quantity or unit x.

Example:

julia> unitfactor(u"3mV")
0.003
julia> u"3mV"|> unitfactor
0.003
julia> unitfactor(u"mV")
0.001
julia> u"mV"|> unitfactor
0.001

Compare this with the corresponding calculations with Unitful values:

julia> u"1mV" |> u"V" |> Unitful.float
0.001 V

See unitful for the reciprocal operation:

julia> u"1cm" |> unitfactor |> u"cm"
1.0 cm
source
LessUnitful.@ufac_strMacro
@ufac_str

String macro for calculating the unit factor of a quantity or physical constant, see also unitfactor.

Example:

julia> ufac"1mV"
0.001

This also allows to access the physical constants q, c0, μ0, ε0, Z0, G, gn, h, ħ, Φ0, me, mn, mp, μB, Na, k, R, σ, R∞ defined in Unitful.jl.

See @ph_str for an alternative way to access physical constants.

source
LessUnitful.@unitfactorsMacro
@unitfactors

Declare unit factors of units as global constants.

Example

julia> @unitfactors cm;

julia> 3cm
0.03

We can declare multiple unit factors at once:

@unitfactors mm cm km A V
julia> @unitfactors cm mm;

julia> cm+mm
0.011

Compare this with the corresponding calculations with Unitful values:

julia> u"1cm"+u"1mm"|> float
0.011 m
source
LessUnitful.@local_unitfactorsMacro
@local_unitfactors

Declare unit factors of units as local variables.

Example

function f()
    @local_unitfactors cm
    3cm
end
f()
# output
0.03
source

Creating unitful values

LessUnitful.unitfulMethod
unitful(x,unit)

Make number x "unitful", assuming that the value of x is the expression of the quantity as in preferred units. This helps to convert numbers to unitful quantities in a way compatible with unitfactor.

Example

julia> unitful(200,u"kPa")
0.2 kPa
source
LessUnitful.MoreUnitfulModule
    MoreUnitful

Make unitful units callable.

This submodule commits a form of type piracy

This behavior is not exported by default, but needs to be explicitely enabled by using LessUnitful.MoreUnitful. It is not recommended to use this in packages.

source
LessUnitful.MoreUnitful.CallableUnitType
(unit)(x::Real)

Make number x "unitful" by calling unitful.

Example

julia> u"kPa"(200)
0.2 kPa
julia> 200 |> u"kPa"
0.2 kPa

Without LessUnitful.MoreUnitful, the result of this operation would be:

ERROR: DimensionError: kPa and 200 are not dimensionally compatible.

This may be convenient when printing with units:

Instead of

@unitfactors μA mA;
x = 15mA
println(x/μA," μA")
# output
15000.0 μA

one can use

@unitfactors  μA mA
x = 15mA
println(x|>u"μA")  
# output
15000.0 μA

See unitfactor for the reciprocal operation:

julia>  0.05|> u"cm" |> unitfactor
0.05
source

Physical constants

As already described above, the @ufac_str macro can be used to extract the values of physical constants from PhysicalConstants.CODATA2018.

LessUnitful.@phconstantsMacro
@phconstants

Declare numerical values of physical constants as unit factors with respect to unitful preferred units as constants. The information is obtained from PhysicalConstants.CODATA2018

Example:

julia> @phconstants N_A
6.02214076e23

This is equivalent to

const N_A = ustrip(upreferred(PhysicalConstants.CODATA2018.N_A))

and we can "declare" multiple constants

@phconstants AvogadroConstant c_0
source

Ensuring consistency with SI Base units

LessUnitful.ensureSIBaseFunction
ensureSIBase()

Ensure that the preferred units are the SI base units. Calls to Unitful.preferunits after this will have no effect.

julia> ensureSIBase()
true
source

Changing preferred units

By calling Unitful.preferunits the preferred units can be changed from SI base units to e.g. g for mass and cm for length.

In order to be effective, this needs to be called before any invocation of Unitful.upreferred, and as a consequence, before any invocation of macros or functions from the LessUnitful package.

Due to this issue for Julia 1.8 it is advised to evaluate at least one of the unit factors from LessUnitful immediately after calling preferunits.

Moreover, while it is convenient to use e.g. @unitfactors in the global scope of a package, it is important to understand that values in global scope are evaluated during precompilation and cannot be influenced by Unitful.upreferred. Therefore it appears that packages which are designed to work consistently with other defaults than SI base units should avoid the use of @unitfactors and @phconstants. The use of @local_unitfactors and @local_phconstants, and of @ufac_str, unitfactor, unitful does not suffer from this problem, though.