[ANN] FlexUnits.jl 0.5 Unit Simplification and Log-Units

FlexUnits.jl

FlexUnits v0.5.0 is a major new release, aimed at supporting two features:

  1. Unit simplification

  2. Logarithmic units

Unit Simplification

What Unitful does

Unitful.jl propagates units directly. When performing a calculation, it records all the unit symbols and objects and produces a new unit type with all the symbols concatenated. This result is useful for small operations:


using Unitful

julia> r = 5u"V"/2u"A" #Ohms is better but this will do

2.5 V A^-1

However, for longer operations, the results can be unsightly and difficult to reason about, this is why upreferred exists, which gives a dimensional view


julia> p = (1.0u"kg/L" * 9.81u"m/s^2" * 25u"cm") #This is supposed to be a pressure

245.25 kg cm m L^-1 s^-2

julia> upreferred(p) #This is a bit better

2452.5 kg m^-1 s^-2

What FlexUnits does by default

FlexUnits.jl and DynamicQuantities.jl, by default will give you a dimensional view of the result (i.e. upreferred). This gives better results for long operations:


using FlexUnits, .UnitRegistry

julia> p = (1.0u"kg/L" * 9.81u"m/s^2" * 25u"cm")

2452.5 kg/(m s²)

But the results are a lot harder to understand for some units (electrical units are particularly challenging)


julia> r = 5u"V"/2u"A" #Electrical units are opaque

2.5 (m² kg)/(s³ A²)

FlexUnits simplify

FlexUnits now has a simplify function that performs a greedy search algorithm to minimize the number of symbols displayed. This approach is more likely yield understandable results than both alternatives (unit-propagation and dimensional-view).


julia> p = (1.0u"kg/L" * 9.81u"m/s^2" * 25u"cm") |> simplify #This is exactly what I wanted

2452.5 Pa

julia r = 5u"V"/2u"A" |> simplify #And so is this

2.5 Ω

Moreover, if you don’t like typing |> simplify all the time, you can set the display_simplified_units setting


display_simplified_units(true) #Show simplified results

julia> p = (1.0u"kg/L" * 9.81u"m/s^2" * 25u"cm")

2452.5 Pa

julia> r = 5u"V"/2u"A"

2.5 Ω

You can also change the units that the simplification algorithm uses in its search process. You can only have one unit per dimension, so if your unit clashes with an existing unit in that dimension, your unit will replace it.


set_preferred_unit(u"psi") #Simplified pressures are now in PSI

julia> p = (1.0u"kg/L" * 9.81u"m/s^2" * 25u"cm")

0.35570491213617295 psi

Gotchas with display_simplified_units(true)

Simplified units are not displayed by default mainly because WARNING, display_simplified_units(true) does not actually simplify the result. It only displays the result you would get if you did simplify it. Setting simplified units by default could give you unexpected answers for ustrip if your preferred units are not SI.


#Recall p was shown in PSI, but it's actually still in kg/(m s²)

julia ustrip(p)

2452.5

Setting display_simplified_units(true) can also produce errors if you’re displaying quantities and units that don’t subscribe to the default Dimensions object (or its static derivatives).

FlexUnits simplify performance

The greedy algorithm used by simplify does require significant resources to run, and is easily 10x more expensive than Unitful’s unit tracking. However, unlike Unitful’s tracking, simplify does not run automatically during calculations (neither at compile nor run time). Even when display_simplified_units(true) is called, the simplify algorithm only runs when displaying results; this isn’t very noticeable because display is rarely called repeatedly and it already has significant overhead anyway.

Logarithmic Units, the Simple Way

Handling logarithmic units such as decibels comes with some level of controversy. The question is whether a quantity such as 1 dB(W) is a logarithm of a power quantity or merely the logarithmic representation of a power quantity.

  1. If 1 dB(W) is a logarithmic representation of a power quantity: 1 dB(W) + 1 dB(W) = 4.0103 dB(W)

  2. If 1 dB(W) is a an actual logarithm of a power quantity: 1 dB(W) + 1 dB = 2 dB(W²)

FlexUnits consistently adopts the philosophy of the second camp, where a decibel represents the logarithm of a quantity and supplies algebraic tools to manipulate logarithms of quantities (referred to as a LogQuant). Taking this consistent approach makes logarithmic units simpler in FlexUnits than Unitful, as the latter can take either philosophy depending on whether your decibel level is unitless or has units.

If you truly believe that 1 dB(W) + 1 dB(W) = 4.0103 dB(W), fear not! FlexUnits has you covered with the semiring notational convention with \oplus and \ominus symbols that apply the following operations

  1. x ⊕ y = log(exp(x) + exp(y))

  2. x ⊖ y = log(exp(x) - exp(y))

Since exp of a logarithmic quantity is a linear quantity, ⊕ ⊖ simply apply addition/subtraction to the linear quantities and convert the result back to log-space.

Producing logarithmic quantities

One way to produce a LogQuant is by taking a log of a quantity.


julia> q = log(2u"W")

log(2.0 (m² kg)/s³)

Another way is to multiply a number by a logarithmic unit. For example, dB is a LogScale object that can be imported to construct a logarithmic unit


import FlexUnits.dB

julia> q = 30dB(u"W")

log(1000.0000000000016 (m² kg)/s³)

As you can see here, 30 dB(W) is equivalent to 1000 W but it displayed as its logarithm. This helps reinforce how operations are performed based on logarithmic identities. While their logarithms are displayed (to emphasize this algebra), the actual numerical value stored is the logarithmic form


julia> ustrip(log(2u"W"))

0.6931471805599453

Operations on logarithmic quantities

The algebraic rules LogQuant are centered around logarithmic identities.


julia> log(4u"m") + log(4u"s") # log(x) + log(y) = log(x*y)

log(15.999999999999998 (m s))

julia> log(4u"m") - log(4u"s") # log(x) - log(y) = log(x/y)

log(1.0 m/s)

julia> 2log(4u"m") # nlog(x) = log(x^n)

log(15.999999999999998 m²)

We also make use of the ⊕ and ⊖ operators that, in this context, commonly refers to adding/subtracting linearized values and transforming back to log space. It’s not exported by default as this symbol could be used by other packages to mean something else.


import FlexUnits: ⊕, ⊖

julia> log(8u"m") ⊕ log(4u"m") #Observe that the linear addition happened

log(12.0 m)

julia> log(8u"m") ⊖ log(4u"m") #Observe that linear subtraction happened

log(3.9999999999999982 m)

Logarithmic quantities can be converted back to regular quantities using quantity, linquant, ubase or exp


julia> (linquant(log(4u"m")), quantity(log(4u"m")), exp(log(4u"m")), ubase(log(4u"m")))

(4.0 m, 4.0 m, 4.0 m, 4.0 m)

Simplification

Simplification on logarithmic units produces decibels by default


display_simplified_units(true)

julia> log(1u"W") + log(1u"W")

0.0 dB(W²)

julia> 1dB(u"W") + 1dB(u"W")

2.0 dB(W²)

You can change this setting to Nepers if you prefer


import FlexUnits.Np

set_preferred_logscale(Np)

julia> 1dB(u"W") + 1dB(u"W")

0.4605170185988092 Np(W²)

julia> 1Np(u"W") + 1Np(u"W")

2.0 Np(W²)

Registering logarithmic units

The default unit registry can only register affine units, which is also usually sufficient for working logarithmic units unless you need to parse strings to produce logarithmic units. In such cases, you will need to register logarithmic units with the LogUnitRegistry instead. This registry can hold both affine and logarithmic units, but uparse can introduce performance issues because the output is a Union. WARNING, because multiplying uparse outputs can produce a Quantity or a LogQuant, based on the string value, it’s recommended that you use explicit constructors like quantity or ubase to always produce linear quantities, or logquant or logubase always produce logarithmic units.


using FlexUnits, .LogUnitRegistry

import FlexUnits.dB

register_unit("dB_V" => dB(u"V"))

julia> 10uparse("dB_V")

log(10.000000000000002 (m² kg)/(s³ A))

julia> 10uparse("V")

10.0 (m² kg)/(s³ A)

julia> ubase(10, uparse("dB_V"))

10.000000000000002 (m² kg)/(s³ A)

julia> logubase(10, uparse("V"))

log(10.000000000000002 (m² kg)/(s³ A))

julia> 10u"dB_V"

log(10.000000000000002 (m² kg)/(s³ A))

1 Like