LessUnitful
— ModuleLessUnitful
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 submoduleLessUnitful.MoreUnitful
. So if something likex=1|>u"cm"
is required (which should give 100cm because1
is assumed to be a value in the SI Basic units in this method), one needsLessUnitful.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 ofUnitful.jl
. Just replace this byph"N_A"*ph"e"
.
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 provideunitfactor(length)
to the "unitless" code
- Declare
- Data output can go as follows - assume result
p
is a pressure:- Declare
@unitfactors kPa
and doprintln("p= ",p/kPa,"kPa")
- Alternatively, do
println(unitful(p,u"kPa"))
, or (withLessUnitful.MoreUnitful
):println(p|> u"kPa")
- Declare
Obtaining unit factors
LessUnitful.unitfactor
— Functionunitfactor(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
LessUnitful.@ufac_str
— Macro@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.
LessUnitful.@unitfactors
— Macro@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
LessUnitful.@local_unitfactors
— Macro@local_unitfactors
Declare unit factors of units as local variables.
Example
function f()
@local_unitfactors cm
3cm
end
f()
# output
0.03
Creating unitful values
LessUnitful.unitful
— Methodunitful(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
LessUnitful.MoreUnitful
— Module MoreUnitful
Make unitful units callable.
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.
LessUnitful.MoreUnitful.CallableUnit
— Type(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
Physical constants
As already described above, the @ufac_str
macro can be used to extract the values of physical constants from PhysicalConstants.CODATA2018.
LessUnitful.@ph_str
— Macro@ph_str
String macro for calculating the unit factor of a physical constant from PhysicalConstants.CODATA2018
julia> ph"N_A"
6.02214076e23
LessUnitful.@phconstants
— Macro@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
LessUnitful.@local_phconstants
— Macro@local_phconstants
Like @phconstants
but declares a local variable.
Example:
function f()
@local_phconstants N_A
N_A
end
f()
# output
6.02214076e23
Ensuring consistency with SI Base units
LessUnitful.ensureSIBase
— FunctionensureSIBase()
Ensure that the preferred units are the SI base units. Calls to Unitful.preferunits
after this will have no effect.
julia> ensureSIBase()
true
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.