# DynamicQuantities.jl v0.7.0: efficient and type-stable physical quantities

**URL:** <https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709>\
**Category:** Package Announcements\
**Tags:** package, physics, unitful\
**Created:** [September 9, 2023, 11:48pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709 "2023-09-09T23:48:08Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [September 9, 2023, 11:48pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/1 "2023-09-09T23:48:08Z")

</div>

Happy to share [DynamicQuantities.jl](https://github.com/SymbolicML/DynamicQuantities.jl) v0.7.0!

[![image](https://global.discourse-cdn.com/julialang/original/3X/6/2/62c7911a39181ff7612190035e697abf83c278e2.png)](https://github.com/SymbolicML/DynamicQuantities.jl)

[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://symbolicml.org/DynamicQuantities.jl/dev/) [![Build Status](https://github.com/SymbolicML/DynamicQuantities.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/SymbolicML/DynamicQuantities.jl/actions/workflows/CI.yml?query=branch%3Amain) [![Coverage](https://coveralls.io/repos/github/SymbolicML/DynamicQuantities.jl/badge.svg?branch=main)](https://coveralls.io/github/SymbolicML/DynamicQuantities.jl?branch=main) [![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)

The package has had some major changes over the last few versions, so I think it deserved a new post.

**DynamicQuantities defines a simple statically-typed `Quantity` type for storing physical units.**

Physical dimensions are stored as a _value_, as opposed to a parametric type, as in [Unitful.jl](https://github.com/PainterQubits/Unitful.jl). This is done to allow for calculations where physical dimensions are not known at compile time.

Changes since the [last post](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963) (described more below)

- Performance improvements (via better compiler inlining)
- Units, and unit parsing via the `@u_str` macro
- Physical constants, and constant parsing via the `Constants.*` prefix (also in `@u_str`)
- `SymbolicDimensions` for working with symbolic units and constants
  - Standard `Dimensions` will eagerly convert to SI units; this avoids it.
  - Symbolic unit/constant parsing via the `@us_str` macro.

- `QuantityArray <: AbstractArray` for efficiently storing arrays of quantities that have the same units
- Extensions for ScientificTypes.jl, LinearAlgebra.jl, as well as an extension to convert to/from Unitful.jl quantities
- `AbstractQuantity` and `AbstractDimensions` for extending behavior or defining custom spaces of physical dimensions

Thanks to contributions from Gaurav Arya @odow @Oscar_Smith @j-fu, and suggestions/feedback from @non-Jedi @ChrisRackauckas @mcabbott and many others in the last thread and on GitHub!

## Performance

DynamicQuantities can greatly outperform Unitful when the compiler cannot infer dimensions in a function:

```julia
julia> using BenchmarkTools, DynamicQuantities; import Unitful

julia> dyn_uni = 0.2u"m^0.5 * kg * mol^3"
0.2 m¹ᐟ² kg mol³

julia> unitful = convert(Unitful.Quantity, dyn_uni)
0.2 kg m¹ᐟ² mol³

julia> f(x, i) = x ^ i * 0.3;

julia> @btime f($dyn_uni, 1);
  2.750 ns (0 allocations: 0 bytes)

julia> @btime f($unitful, 1);
  2.718 μs (30 allocations: 1.34 KiB)

```

**Note the μ and n: this is a 1000x speedup.** Here, the DynamicQuantities quantity object 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, if the dimensions in your function _can_ be inferred by the compiler, then you can get better speeds with Unitful:

```julia
julia> g(x) = x ^ 2 * 0.3;

julia> @btime g($dyn_uni);
  1.875 ns (0 allocations: 0 bytes)

julia> @btime g($unitful);
  1.500 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.

**Aside** : The compiler seems to be pretty good at inlining things especially with the recent update (thanks to Gaurav Arya for helping get this through!), so this performance gap seems to have shrunk for even statically typed calculations. This above calculation used to be 5x in favor of Unitful. However, this depends on compiler constant propagation so it is calculation dependent how big this gap would be.

## Types

At the heart of the package is just two immutable structs:

```julia
struct Dimensions{R<:Real} <: AbstractDimensions{R}
    length::R
    mass::R
    time::R
    current::R
    temperature::R
    luminosity::R
    amount::R
end

struct Quantity{T,D<:AbstractDimensions} <: AbstractQuantity{T,D}
    value::T
    dimensions::D
end

```

The `R` type here is typically a rational-like number (the default is an internal `FixedRational` type – a rational number with fixed denominator which gives much faster operations than `Rational`).

What’s nice about the abstract interface is you can just write a custom `AbstractDimensions` type with the physical dimensions you want to use, and via the use of @oxinabox’s Tricks.jl, `static_fieldnames` is used to compile all of the unit propagation methods at first call. So e.g., `struct MyDimensions{R} <: AbstractDimensions{R}; length::R; time::R; end` would work out of the box! (`Quantity(0.3, MyDimensions, length=1, mass=-1)` would then be `0.3 m/s`, and you could calculate away)

## Generic usage

You can create a `Quantity` object by using the convenience macro `u"..."`:

```julia
julia> x = 0.3u"km/s"
300.0 m s⁻¹

julia> room_temp = 100u"kPa"
100000.0 m⁻¹ kg s⁻²

```

This supports a wide range of SI base and derived units, with common prefixes.

You can also construct values explicitly with the `Quantity` type, with a value and keyword arguments for the powers of the physical dimensions (`mass`, `length`, `time`, `current`, `temperature`, `luminosity`, `amount`):

```julia
julia> x = Quantity(300.0, length=1, time=-1)
300.0 m s⁻¹

```

Elementary calculations with `+, -, *, /, sqrt, cbrt, abs` are supported, and `^` will use `rationalize` to get a reasonable power from exponentiation:

```julia
julia> x * y
12600.0 m kg s⁻¹

julia> x / y
7.142857142857143 m kg⁻¹ s⁻¹

julia> x ^ 3
2.7e7 m³ s⁻³

julia> x ^ -1
0.0033333333333333335 m⁻¹ s

julia> sqrt(x)
17.320508075688775 m¹ᐟ² s⁻¹ᐟ²

julia> x ^ 1.5
5196.152422706632 m³ᐟ² s⁻³ᐟ²

```

Each of these values has the same type, which means we don’t need to perform type inference at runtime.

Furthermore, we can do dimensional analysis by detecting `DimensionError`:

```julia
julia> x + 3 * x
1.2 m¹ᐟ² kg

julia> x + y
ERROR: DimensionError: 0.3 m¹ᐟ² kg and 10.2 kg² s⁻² have incompatible dimensions

```

The dimensions of a `Quantity` can be accessed either with `dimension(quantity)` for the entire `Dimensions` object:

```julia
julia> dimension(x)
m¹ᐟ² kg

```

or with `umass`, `ulength`, etc., for the various dimensions:

```julia
julia> umass(x)
1//1

julia> ulength(x)
1//2

```

Finally, you can strip units with `ustrip`:

```julia
julia> ustrip(x)
0.2

```

### Constants

There are a variety of physical constants accessible  
via the `Constants` submodule:

```julia
julia> Constants.c
2.99792458e8 m s⁻¹

```

These can also be used inside the `u"..."` macro:

```julia
julia> u"Constants.c * Hz"
2.99792458e8 m s⁻²

```

For the full list, see the [docs](https://symbolicml.org/DynamicQuantities.jl/dev/constants/).

### Symbolic Units

You can also choose to not eagerly convert to SI base units, instead leaving the units as the user had written them. For example:

```julia
julia> q = 100us"cm * kPa"
100.0 cm kPa

julia> q^2
10000.0 cm² kPa²

```

You can convert to regular SI base units with `expand_units`:

```julia
julia> expand_units(q^2)
1.0e6 kg² s⁻⁴

```

This also works with constants:

```julia
julia> x = us"Constants.c * Hz"
1.0 Hz c

julia> x^2
1.0 Hz² c²

julia> expand_units(x^2)
8.987551787368176e16 m² s⁻⁴

```

This dimensions type works a bit differently as it stores all the dimensions in a sparse vector ([source](https://github.com/SymbolicML/DynamicQuantities.jl/blob/41b9e61ecef9c319d386650e2ca76ccd4dfb0882/src/symbolic_dimensions.jl#L38) for the curious). All unit calculations are performed as operations on this sparse vector (this is convenient for user interfaces, and still reasonably fast, but not as fast as the regular immutable `Dimensions`).

### Arrays

For working with an array of quantities that have the same dimensions, you can use a `QuantityArray`:

```julia
julia> ar = QuantityArray(rand(3), u"m/s")
3-element QuantityArray(::Vector{Float64}, ::Quantity{Float64, Dimensions{DynamicQuantities.FixedRational{Int32, 25200}}}):
 0.2729202669351497 m s⁻¹
 0.992546340360901 m s⁻¹
 0.16863543422972482 m s⁻¹

```

This `QuantityArray` is a subtype `<:AbstractArray{Quantity{Float64,Dimensions{...}},1}`,  
meaning that indexing a specific element will return a `Quantity`:

```julia
julia> ar[2]
0.992546340360901 m s⁻¹

julia> ar[2] *= 2
1.985092680721802 m s⁻¹

julia> ar[2] += 0.5u"m/s"
2.485092680721802 m s⁻¹

```

Which performs dimension checks.

This has a custom broadcasting interface which allows the compiler to avoid redundant dimension calculations and dimensional analysis, relative to if you had simply used an array of quantities:

```julia
julia> f(v) = v^2 * 1.5;

julia> @btime $f.(xa) setup=(xa = randn(100000) .* u"km/s");
  109.500 μs (2 allocations: 3.81 MiB)

julia> @btime $f.(qa) setup=(xa = randn(100000) .* u"km/s"; qa = QuantityArray(xa));
  50.917 μs (3 allocations: 781.34 KiB)

```

So we can see the `QuantityArray` version saves on both time and memory.

### Unitful

DynamicQuantities allows you to convert back and forth from Unitful.jl:

```julia
julia> import Unitful; import DynamicQuantities

julia> x = 0.5Unitful.u"km/s"
0.5 km s⁻¹

julia> y = convert(DynamicQuantities.Quantity, x)
500.0 m s⁻¹

julia> y2 = y^2 * 0.3
75000.0 m² s⁻²

julia> x2 = convert(Unitful.Quantity, y2)
75000.0 m² s⁻²

julia> x^2*0.3 == x2
true

```

### Other

One other thing to mention, which is actually part of the reason I started working on this package: [SymbolicRegression.jl](https://github.com/MilesCranmer/SymbolicRegression.jl/) now supports dimensional constraints as part of equation discovery searches! This feature required the creation of this package, as expressions are runtime generated so Unitful.jl could not be used efficiently. Example here: [https://astroautomata.com/SymbolicRegression.jl/dev/examples/#.-Dimensional-constraints](https://astroautomata.com/SymbolicRegression.jl/dev/examples/#.-Dimensional-constraints)

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [September 11, 2023, 8:20pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/2 "2023-09-11T20:20:27Z")

</div>

Thanks for the package! It’ll also be useful outside of the diffeq ecosystem sometimes.

I wonder how compatible its interface is with Unitful? That is, can one take code, replace `using Unitful` with `DynamicQuantities`, and be done? Or, is this among future goals?

Also, just a minor comment:  
The name clearly conveys what this package does, but the short description doesn’t really differentiate it from `Unitful`.

> fast and lightweight physical quantities

> simple statically-typed type for storing physical units

Both are applicable to Unitful as-is. Maybe, highlight dynamic/runtime/untyped nature more?

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [September 12, 2023, 12:23am UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/3 "2023-09-12T00:23:46Z")

</div>

> That is, can one take code, replace `using Unitful` with `DynamicQuantities`, and be done?

I think this is mostly there? The same core symbols: `@u_str`, `uparse`, and `ustrip` can be used the same way (mostly; I don’t allow `u"(m, s)"` for example as it would require type instability), and `@u_str` creates a `Quantity`. This `Quantity` type in both packages forwards all the basic mathematical methods you would expect. You can also dispatch on `Quantity{Float64}` in both packages to expect quantities with a `Float64` value type. But please raise an issue if there are any major incompatibilities I’m missing! I would like it to be compatible aside from the major design differences –

The main difference which we are stuck with is from how they treat dimensions at the type level (if that is what you are asking). So if you are dispatching on dimensions, like

```julia
julia> using Unitful

julia> x = 1.5u"m/s"
1.5 m s⁻¹

julia> typeof(x)
Quantity{Float64, 𝐋 𝐓⁻¹, Unitful.FreeUnits{(m, s⁻¹), 𝐋 𝐓⁻¹, nothing}}

```

and then dispatching on `<:Quantity{Float64, 𝐋 𝐓⁻¹}`, then you would have to change that code to look at the values of the quantity instead. Because DynamicQuantities.jl does this:

```julia
julia> using DynamicQuantities

julia> x = 1.5u"m/s"
1.5 m s⁻¹

julia> typeof(x)
Quantity{Float64, Dimensions{FixedRational{Int32, 25200}}}

```

This is the same type regardless of the units used. The dimensions are stored in `dimension(x)` which is a simple immutable struct:

```julia
julia> Base.propertynames(dimension(x))
(:length, :mass, :time, :current, :temperature, :luminosity, :amount)

```

which are all rationals.

> [@aplavin](#):
>
> The name clearly conveys what this package does, but the short description doesn’t really differentiate it from `Unitful`.

Good point, thanks! Any suggestions for wording it would be much appreciated. Maybe something like _DynamicQuantities defines a simple **type-consistent** `Quantity` for storing physical units_?

---

<div class="post-metadata">

**Author:** ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)\
**Post date:** [September 12, 2023, 12:35am UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/4 "2023-09-12T00:35:46Z")

</div>

This is very nice indeed. I do suspect however that this is not for large-scale simulations? There is a penalty to pay when storing and manipulating a large number of numbers with units?

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [September 12, 2023, 2:35am UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/5 "2023-09-12T02:35:59Z")

</div>

Thanks. So If you are confident that there are no type instabilities due to failure to infer units during compilation (which can _really_ hurt performance), or even things like multiple units in a single array (which IIRC might promote to `Array{Any}`?), then yes Unitful will be faster.

How much faster depends on the calculation and how much inlining the compiler does.

Sometimes the compiler can propagate unit values pretty well by itself, without needing to encode everything in the type (as seen in the above comparison). And the `QuantityArray` means it might choose to process the units only once for the entire array. For storage costs, this would be a single `Dimensions` for the whole array — there is not a separate `Dimensions` stored for each number. So only 32 bytes extra per entire array.

My gut feeling is that if this is a highly tuned and type stable simulation with a few expensive kernels that are called repeatedly, then I might stick with Unitful as it would completely eliminate the cost. But since the API is pretty similar you might as well try and see 😉

The other pro is you can take full advantage of precompilation, since you don’t need to compile the simulation for all possible units, only once for a generic `Quantity`.

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [September 12, 2023, 6:33pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/6 "2023-09-12T18:33:14Z")

</div>

> [@MilesCranmer](#):
>
> How much faster depends on the calculation and how much inlining the compiler does.

Not inlining but branch prediction. The switch statements of “if this unit” etc. are actually “free” when running in a loop, because your CPU does speculative execution and so it just starts executing one branch under the assumption that it’s correct and if it’s correct there’s no cost. When run in a loop, this has a process of effectively “learning” the branches to predict (internal on the CPU, some cool stuff) and so the branches predict better and the cost of having the branches is effectively zero. See:

> **[Branch predictor](https://en.wikipedia.org/wiki/Branch_predictor)**
>
> In computer architecture, a branch predictor is a digital circuit that tries to guess which way a branch (e.g., an if–then–else structure) will go before this is known definitively. The purpose of the branch predictor is to improve the flow in the instruction pipeline. Branch predictors play a critical role in achieving high performance in many modern pipelined microprocessor architectures.
> Two-way branching is usually implemented with a conditional jump instruction. A conditional jump can eithe...

Given this fact, I think way too many people overestimate the utility of putting this unit information into the type domain. That said, the existence of branches do cause LLVM to omit SIMD, and so if you did have a loop that SIMD’d very well you would pay the price of that being eliminated because the runtime checks would interfere with the assumptions of SIMD.

But that said, for a lot of ODE/PDE models, the main cost is in the LU-factorizations and if that just ignores units, this shouldn’t be too noticable of a hit to the `f` evaluations and so it shouldn’t actually show up as a major contributor in “most” profiles. You can definitely find some cases where this matters (very small cases getting very good SIMD), but I think people should really go to this technique first before trying Unitful in almost every case because Unitful is a lot more work for a performance optimization that only exists in a minority of cases (especially because many cases with Unitful will hit `Array{Any}` and actually be orders of magnitude slower due to dynamic dispatching).

Moral of the story, DynamicQuantities.jl is really good.

---

<div class="post-metadata">

**Author:** ![tictaccat](https://avatars.discourse-cdn.com/v4/letter/t/9f8e36/32.png) [@tictaccat](https://discourse.julialang.org/u/tictaccat)\
**Post date:** [September 13, 2023, 10:11pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/7 "2023-09-13T22:11:13Z")

</div>

I think the issue is that the additional overhead due to units is often due to running something like `unit1 * unit2 = unit3` repeatedly in a loop? This computation isn’t a branch but rather some arithmetic operations that are redundantly performed each iteration.

Using a `QuantityArray` we could hope for this to be lifted out of the loop since the compiler should in theory be able to prove that `unit1` and `unit2` are loop invariant, but this only seems to happen in limited cases dependent upon inlining heuristics.

(Spitballing here, but if pure branch computations are free, I wonder if we could optimize `unit1 * unit2` to be a pure branch computation in common cases via a lookup table-esque approach…)

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [September 13, 2023, 10:29pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/8 "2023-09-13T22:29:54Z")

</div>

The compiler doesn’t lift it out in this case, but branch prediction can make the cost trivial anyways because there is no cost to well-predicted branches, and a simple structure will predict well. Again, what you lose in performance is just SIMD.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [November 23, 2023, 8:49pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/9 "2023-11-23T20:49:33Z")

</div>

Some more updates as of v0.10

## New typing system

There are now multiple quantity types which act identically, but let you work with different subtypes:

- `Quantity <: AbstractQuantity <: Number`
  - The default quantity returned by `0.5u"m/s"`, which is subtyped to `Number`.

- `GenericQuantity <: AbstractGenericQuantity <: Any`
  - Do you need to put units on some non-numerical type? Then you want `GenericQuantity`

- `RealQuantity <: AbstractRealQuantity <: Real`
  - Many packages throughout Julia require `Real` input. Although physical quantities are not technically real, it can still be useful for compatibility to pass them as if they were. `RealQuantity` lets you do that.

- All three of these are of course compatible with `QuantityArray`, and can take any `AbstractDimensions` to store the dimensions.

```julia
julia> x = 0.5u"m/s"
0.5 m s⁻¹

julia> x_re = RealQuantity(x)::Real
0.5 m s⁻¹

julia> y_gen = GenericQuantity("hello", length=1, time=-1)
(hello) m s⁻¹

```

We also get automatic promotion:

```julia-auto
julia> x = RealQuantity(0.5u"m/s")
0.5 m s⁻¹

julia> y = x * (1 + 2im)
(0.5 + 1.0im) m s⁻¹

julia> typeof(y)
Quantity{ComplexF64, Dimensions{DynamicQuantities.FixedRational{Int32, 25200}}}

```

the behavior of which can be overloaded for any custom hierarchy of quantity types.

Thanks to Guarav Arya for his help on this.

## New methods

Many other numerical methods which require dimensionless numbers, such as `exp` or `sin`, can now take a quantity as input (required because `DynamicQuantities.Quantity` can be dimensionless - it doesn’t automatically convert to `Float64` due to the potential for type instability).

The overloaded method for dimensionless functions will simply check if the quantity is dimensionless or not, and then evaluate:

```julia
julia> x = 0.5u"m"
0.5 m

julia> y = 10u"Constants.Mpc"
3.085677581491367e23 m

julia> exp(x/y)
1.0

```

This means we can also have ranges of quantities now (via `Base.rem`)

```julia
julia> for x in 0u"km":1.5u"km":10u"km"
           @show x^2
       end
x ^ 2 = 0.0 m²
x ^ 2 = 2.25e6 m²
x ^ 2 = 9.0e6 m²
x ^ 2 = 2.025e7 m²
x ^ 2 = 3.6e7 m²
x ^ 2 = 5.625e7 m²
x ^ 2 = 8.1e7 m²

```

(Although this should probably use a `QuantityArray`… PRs always appreciated )

## Better perf

QuantityArray performance seems pretty solid now, and lets you wrap arrays of arbitrary data with quantities via `GenericQuantity`. Here’s summation on a regular array compared to an array of physical quantities:

```julia-auto
julia> struct Coords
           x::Float64
           y::Float64
       end

julia> Base.:+(a::Coords, b::Coords) = Coords(a.x+b.x, a.y+b.y)

julia> Base.:*(a::Coords, b::Number) = Coords(a.x*b, a.y*b)

julia> Base.:*(a::Number, b::Coords) = Coords(a*b.x, a*b.y)

julia> @btime(
    sum(array),
    setup=(N=1000; array=[Coords(rand(), rand()) for i=1:N]
)
  828.000 ns (0 allocations: 0 bytes)
Coords(509.5939555433635, 494.56896926222123)

julia> @btime(
    sum(array),
    setup=(N=1000; array=QuantityArray([GenericQuantity(Coords(rand(), rand()), dimension(u"m/s")) for i=1:N])
)
  843.750 ns (0 allocations: 0 bytes)
(Coords(504.7718424677944, 482.0449323023619)) m s⁻¹

```

Thanks to all the contributors thus far 😃

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [March 11, 2024, 4:07pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/10 "2024-03-11T16:07:05Z")

</div>

# More updates as of v0.13.0!

## Unit conversion operator

There is now a dedicated infix operator for converting units, thanks to @j-fu:

```julia
julia> 5e-9u"m" |> us"nm"
5.0 nm

```

which is equivalent to `uconvert(us"nm", 5e-9u"m")`.

## Custom units

In addition to custom dimension spaces which you can define by inheriting from `AbstractDimensions`:

```julia
julia> struct CookiesAndMilk{R} <: AbstractDimensions{R}
           cookies::R
           milk::R
       end

julia> cookie_rate = Quantity(0.9, CookiesAndMilk(cookies=1, milk=-1))
0.9 cookies milk⁻¹

julia> total_milk = Quantity(103, CookiesAndMilk(milk=1))
103 milk

julia> total_cookies = cookie_rate * total_milk
92.7 cookies

```

You can also easily define custom units in SI dimensions (i.e., built-in `Dimensions`), thanks to @ven-k:

```julia
julia> @register_unit OneFiveV 1.5u"V"

```

which lets you use this in normal macros:

```julia
julia> x = us"OneFiveV"
1.0 OneFiveV

julia> x * 10u"A" |> us"W"
15.0 W

julia> 3us"V" |> us"OneFiveV"
2.0 OneFiveV

```

Note that any custom units registered with `@register_unit` are localized to a particular module and do not leak into the global namespace.

## Angular units

There are now angular units such as `rad`, `arcmin`, `deg`, and solid angle unit `sr`. Just note that these assume the SI definition that `1 rad = 1 = 1 sr`, but you can always track them as symbolic units like:

```julia-auto
julia> θ_moon = 31us"arcmin"
31.0 arcmin

julia> d_moon = 384_400us"km"
384400.0 km

julia> r_moon = d_moon * θ_moon / 2 |> uexpand
1.7331701248721024e6 m

julia> r_moon |> us"Constants.R_earth"
0.2717376844000725 R_earth

```

## Symbolic unit exports

The primitive symbolic units are now stored using a single integer, and only create allocations when you start doing operations with them. This means that all symbolic units have now been made available for direct import:

```julia
julia> using DynamicQuantities: SymbolicUnits as U

julia> U.km / U.yr
1.0 km yr⁻¹

```

Thanks to @devmotion for help in implementing this.

## Other changes

You can now precompile statements made with `us"..."` or `u"..."` as they no longer rely on `eval` but on an internal parser. Thanks to @YingboMa for tips.

There is now a proper `isapprox` for arrays of quantities and `QuantityArray` which allows `atol` with unit specification. Thanks to @mike.ingold for this.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [March 11, 2024, 5:31pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/11 "2024-03-11T17:31:42Z")

</div>

Great seeing this gaining more and more features, becoming more and more complete Unitful alternative for those cases when runtime units are required!

> [@MilesCranmer](#):
>
> There are now angular units such as `rad`, `arcmin`, `deg`, and solid angle unit `sr`. Just note that these assume the SI definition that `1 rad = 1 = 1 sr`, but you can always track them as symbolic units like:

However, a bit unfortunate that these error-prone defaults proliferate `:(`  
(same as Unitful, but unlike eg astropy)  
Allows for completely meaningless conversions:

```julia
julia> (u"W" / 30us"arcmin^2") |> us"W"
393936.76200140937 W

```

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [March 11, 2024, 5:55pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/12 "2024-03-11T17:55:36Z")

</div>

> [@aplavin](#):
>
> However, a bit unfortunate that these error-prone defaults proliferate `:(`  
> (same as Unitful, but unlike eg astropy)  
> Allows for completely meaningless conversions:

I don’t think there’s really a “correct” answer here. `1 rad = 1` is the SI standard: [Radian - Wikipedia](https://en.wikipedia.org/wiki/Radian#CITEREFInternational_Bureau_of_Weights_and_Measures2019), so this statement of `u"W"/u"rad" == u"W"` is 100% correct according to the international system of units, even though it might not be suitable for a particular task.

In astropy their choice to violate SI means that you get things like:

```python
>>> from astropy import units as u

>>> theta = 0.5*u.rad

>>> distance = 100*u.km

>>> (theta*distance).to(u.km)
...
astropy.units.core.UnitConversionError: 'km rad' and 'km' (length) are not convertible

```

It’s really just a choice someone has to make. Astronomy, as you know, has countless examples of nomenclature that completely violate SI. (Sometimes it’s very convenient though!)

I’d rather stick 100% to SI though (even though I myself have an astronomy background). There are just too many silent bugs that can arise from mixing standards.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [March 11, 2024, 6:25pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/13 "2024-03-11T18:25:32Z")

</div>

It’s much easier to tell the computer to ignore radians when they are present (eg, see astropy so-called “equivalencies”) than to determine what angular units should be in the quantity when there are none.  
So, the astropy’s approach makes both usecases (want angular units present or ignored) possible and convienient, while Unitful’s only allows one.

> [@MilesCranmer](#):
>
> I’d rather stick 100% to SI though (even though I myself have an astronomy background). There are just too many silent bugs that can arise from mixing standards.

Hard to imagine any silent bugs arising from more strict conversion rules, with more constraints. But bugs in the other direction are clearly possible (see my last post).

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [March 11, 2024, 6:30pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/14 "2024-03-11T18:30:39Z")

</div>

> [@aplavin](#):
>
> It’s much easier to tell the computer to ignore radians when they are present (eg, see astropy so-called “equivalencies”) than to determine what angular units should be in the quantity when there are none.  
> So, the astropy’s approach makes both usecases (want angular units present or ignored) possible and convienient, while Unitful’s only allows one.

Wait, why can’t you do this with the existing symbolic units? Like `us"arcmin"`. Only `u"arcmin"` will automatically convert to SI equivalencies.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [March 11, 2024, 6:32pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/15 "2024-03-11T18:32:01Z")

</div>

This is with “symbolic units”:

> [@aplavin](#):
>
> ```julia-auto
> julia> (u"W" / 30us"arcmin^2") |> us"W"
> 393936.76200140937 W
> 
> ```

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [March 11, 2024, 6:34pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/16 "2024-03-11T18:34:20Z")

</div>

It’s not:

```julia
julia> typeof(u"W" / 30us"arcmin^2")
Quantity{Float64, Dimensions{DynamicQuantities.FixedRational{Int32, 25200}}}

```

You need to write `us"W" / 30us"arcmin^2"` to keep it in symbolic units. With `u"W"` it will promote to regular SI units.

```julia
julia> typeof(us"W" / 30us"arcmin^2")
Quantity{Float64, SymbolicDimensions{DynamicQuantities.FixedRational{Int32, 25200}}}

```

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [March 11, 2024, 6:35pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/17 "2024-03-11T18:35:02Z")

</div>

> [@MilesCranmer](#):
>
> You need to write `us"W" / 30us"arcmin^2"` to keep it in symbolic units.

Same result anyways:

```julia
julia> (us"W" / 30us"arcmin^2") |> us"W"
393936.7620014093 W

```

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [March 11, 2024, 6:38pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/18 "2024-03-11T18:38:33Z")

</div>

Because you doing explicit conversion. If you write it out it stays there.

```julia
julia> x = us"W" / 30us"arcmin^2"
0.03333333333333333 W arcmin⁻²

julia> y = x * 10us"arcmin"
0.3333333333333333 W arcmin⁻¹

julia> y * 0.5us"arcmin"
0.16666666666666666 W

```

Does this help?

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [March 11, 2024, 6:40pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/19 "2024-03-11T18:40:46Z")

</div>

A main/major point of using unitful values instead of unitless (= raw numbers) is that only unitfully-meaningful conversions are allowed. Clearly, W/arcmin^2 being convertible to W doesn’t help at all `:)`

I had this bug quite a few times (with Unitful, but same behavior here): forgot to multiply by an angle, got correct units in the end, but completely meaningless results.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [March 11, 2024, 6:47pm UTC](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709/20 "2024-03-11T18:47:46Z")

</div>

I mean, `W/arcmin^2` is by definition equivalent dimension to `W` in SI. I guess here you just mean it’s not convenient for astronomy (which is very fair!).

I guess you could try to do something like this if you want?

```julia
julia> struct AngleDimensions{R} <: AbstractDimensions{R}
           length::R
           mass::R
           time::R
           current::R
           temperature::R
           luminosity::R
           amount::R
           angle::R
       end

julia> y = Quantity(2.0, AngleDimensions(mass=1, length=2, time=-3))
2.0 m² kg s⁻³

julia> x = Quantity(0.5, AngleDimensions(angle=2))
0.5 angle²

julia> y / x
4.0 m² kg s⁻³ angle⁻²

```

It will need converters from regular `Dimensions` but the operations themselves should work out of the box.

[Next page](https://discourse.julialang.org/t/dynamicquantities-jl-v0-7-0-efficient-and-type-stable-physical-quantities/103709.md?page=2)
