Happy to share *DynamicQuantities.jl*, a library that defines a simple, statically-typed `Quantity`

object for working with physical units in Julia.

Physical quantities are stored as *values*, as opposed to the parametrically-typed units in Unitful.jl. This is done to allow for calculations where physical dimensions are not inferrable at compile time.

### Performance

These type-stable quantities can greatly outperform those in Unitful when the compiler cannot infer dimensions:

```
julia> using BenchmarkTools, DynamicQuantities; import Unitful
julia> dynamic_q = Quantity(0.2, mass=1, length=0.5, amount=3)
0.2 𝐋 ¹ᐟ² 𝐌 ¹ 𝐍 ³
julia> unitful = convert(Unitful.Quantity, dynamic_q)
0.2 kg m¹ᐟ² mol³
julia> f(x, i) = x ^ i * 0.3;
julia> @btime f($dynamic_q, i) setup=(i=rand(1:10));
9.384 ns (0 allocations: 0 bytes)
julia> @btime f($unitful, i) setup=(i=rand(1:10));
29.667 μs (42 allocations: 1.91 KiB)
```

Note the μ and n: this example gets a **>3000x speedup**. DynamicQuantities allows the compiler to build a function that is type stable, while the Unitful quantity object, which stores its dimensions in the type, requires type inference at runtime.

However, note if the dimensions in your function *can* be inferred by the compiler, then you can get better speeds with Unitful:

```
julia> g(x) = x ^ 2 * 0.3;
julia> @btime g($dynamic_q);
6.083 ns (0 allocations: 0 bytes)
julia> @btime g($unitful);
1.958 ns (0 allocations: 0 bytes)
```

While both of these are type stable, because Unitful parametrizes the type on the dimensions, functions can specialize to units and the compiler can optimize away units from the code entirely.

## Usage

You can create a `Quantity`

object with a value and keyword arguments for the powers of the physical dimensions (`mass`

, `length`

, `time`

, `current`

, `temperature`

, `luminosity`

, `amount`

):

```
julia> x = Quantity(0.3, mass=1, length=0.5)
0.3 𝐋 ¹ᐟ² 𝐌 ¹
```

Elementary calculations with `+, -, *, /, ^, sqrt, cbrt, abs`

are supported, letting you perform calculations on quantities just as you would with Unitful.

```
julia> x ^ 1.5
0.1643167672515498 𝐋 ³ᐟ⁴ 𝐌 ³ᐟ²
julia> x * Quantity(10.2, mass=2, time=-2)
3.0599999999999996 𝐋 ¹ᐟ² 𝐌 ³ 𝐓 ⁻²
julia> x ^ 3
0.027 𝐋 ³ᐟ² 𝐌 ³
julia> x ^ -1
3.3333333333333335 𝐋 ⁻¹ᐟ² 𝐌 ⁻¹
julia> sqrt(x)
0.5477225575051661 𝐋 ¹ᐟ⁴ 𝐌 ¹ᐟ²
```

Each return value has the same type as `x`

, which means no type inference is needed.

The dimensions of a `Quantity`

can be accessed either with `dimension(quantity)`

for the entire `Dimensions`

object:

```
julia> dimension(x)
𝐋 ¹ᐟ² 𝐌 ¹
```

or with `umass`

, `ulength`

, etc., for specific dimensions. You can strip dimensions with `ustrip`

. You can check for dimensional analysis errors with `valid(x)`

- avoiding a try/catch.

## Units

DynamicQuantities works with quantities which store physical dimensions (think of this as using the SI standard units — and only those) and a value, and does not directly provide a unit system. However, DynamicQuantities has an interface with Unitful: you can use Unitful to parse units, and then use the DynamicQuantities->Unitful extension for conversion:

```
julia> using Unitful: Unitful, @u_str
julia> x = 0.5u"km/s"
0.5 km s⁻¹
julia> y = convert(DynamicQuantities.Quantity, x) # Auto reduces to SI
500.0 𝐋 ¹ 𝐓 ⁻¹
julia> y2 = y^2 * 0.3
75000.0 𝐋 ² 𝐓 ⁻²
julia> x2 = convert(Unitful.Quantity, y2) # Back to SI
75000.0 m² s⁻²
julia> x^2*0.3 == x2 # Same as if we did it in Unitful
true
```

This means you could use Unitful as a user interface, for taking inputs from its vast catalog of physical units, and then DynamicQuantities.jl for type-stable calculations where needed.

## Vectors

Because the types are stable you can have mixed units in a vector:

```
julia> v = [Quantity(randn(), mass=rand(0:5), length=rand(0:5)) for _=1:5]
5-element Vector{Quantity{Float64}}:
2.2054411324716865 𝐌 ³
-0.01603602425887379 𝐋 ⁴ 𝐌 ³
1.4388184352393647
2.382303019892503 𝐋 ² 𝐌 ¹
0.6071392594021706 𝐋 ⁴ 𝐌 ⁴
```

## Internals

DynamicQuantities.jl is only ~300 lines of code. The main two types, `Dimensions`

, and `Quantities`

, are simple structs:

```
import Ratios: SimpleRatio
const R = SimpleRatio{Int} # Faster Rational{Int}
struct Dimensions
length::R
mass::R
time::R
current::R
temperature::R
luminosity::R
amount::R
end
struct Quantity{T}
value::T
dimensions::Dimensions
valid::Bool
end
```

The fields of `Dimensions`

store the powers of each physical dimension of the 7 used in the SI system.

The rest of the library is just setting up helper utilities and overloading math operators.

The library resulted from encouragement from others in this discussion. I thought I’d share it broadly now that it seems to be working. Try it out with

```
] add https://github.com/SymbolicML/DynamicQuantities.jl
```

(Pkg registration t-3 days)

I want to also warmly welcome contributors to this package. These sorts of units packages seem pretty general so I am happy to consider suggestions/PRs.