# \[ANN\] DynamicQuantities.jl: type stable physical quantities

**URL:** https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963
**Category:** Package Announcements
**Tags:** package, announcement
**Created:** [June 6, 2023, 8:07pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963 "2023-06-06T20:07:08Z")
**Posts on this page:** 20
**Page:** 2

<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: [June 14, 2023, 6:58pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/21 "2023-06-14T18:58:19Z")

</div>

DynamicQuantities.jl v0.4 is now a one-stop shop for physical quantities, with a built-in units parser available. Thanks to @odow @Oscar_Smith @mcabbott @j-fu for review and feedback.

You can create a quantity with the `@u_str` macro, like so:

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

julia> randn(3) .* u"GHz"
3-element Vector{Quantity{Float64, DynamicQuantities.FixedRational{Int32, 25200}}}:
 1.558255538781646e9 s⁻¹
 -1.0549637909362864e8 s⁻¹
 1.392099679975791e9 s⁻¹

julia> u"m/s^0.5"
1.0 m s⁻¹ᐟ²

```

The “dimensions” are now printed in terms of the SI base units to avoid misinterpretation.

These are the available units:

**Base** :

- `m`: Length in meters. Available variants: `fm`, `pm`, `nm`, `μm` (/`um`), `cm`, `dm`, `mm`, `km`, `Mm`, `Gm`.
- `g`: Mass in grams. Available variants: `μg` (/`ug`), `mg`, `kg`.
- `s`: Time in seconds. Available variants: `fs`, `ps`, `ns`, `μs` (/`us`), `ms`, `min`, `h` (/`hr`), `day`, `yr`, `kyr`, `Myr`, `Gyr`.
- `A`: Current in Amperes. Available variants: `nA`, `μA` (/`uA`), `mA`, `kA`.
- `K`: Temperature in Kelvin. Available variant: `mK`.
- `cd`: Luminosity in candela. Available variant: `mcd`.
- `mol`: Amount in moles. Available variant: `mmol`.

**Derived** :

- `Hz`: Frequency in Hertz. Available variants: `kHz`, `MHz`, `GHz`.
- `N`: Force in Newtons.
- `Pa`: Pressure in Pascals. Available variant: `kPa`.
- `J`: Energy in Joules. Available variant: `kJ`.
- `W`: Power in Watts. Available variants: `kW`, `MW`, `GW`.
- `C`: Charge in Coulombs.
- `V`: Voltage in Volts. Available variants: `kV`, `MV`, `GV`.
- `F`: Capacitance in Farads.
- `Ω`: Resistance in Ohms. Available variant: `mΩ`. Also available is ASCII `ohm` (with variant `mohm`).
- `T`: Magnetic flux density in Teslas.
- `L`: Volume in liters. Available variants: `mL`, `dL`.
- `bar`: Pressure in bars.

In addition to `@u_str` and `uparse` which evaluate expressions in this namespace, you can also access these explicitly with, e.g., `DynamicQuantities.Units.kW`. All of these “units” are of type `Quantity{Float64,FixedRational{Int32,25200}}`, and `uparse` will return quantities of this type, but you can convert as needed with `convert(::Type{Quantity{T,R}}, ...)`.

The Unitful.jl integration is now just for convenience, but is unnecessary for parsing user input.

---

<div class="post-metadata">

### Author: ![goretkin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goretkin/32/167_2.png) [@goretkin](https://discourse.julialang.org/u/goretkin)
#### Post date: [June 15, 2023, 2:42am UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/22 "2023-06-15T02:42:56Z")

</div>

Regarding encoding units in the “type” domain vs the “value” domain, I wonder about a design that makes it possible to have the best of both worlds. For example, removing the constraint `R <: Real` in

```plaintext
struct Dimensions{R}
    length::R
    mass::R
    time::R
    current::R
    temperature::R
    luminosity::R
    amount::R
end

```

You could define a dimension of length dynamically as `Dimensions(1, 0, 0, 0, 0, 0, 0)`or statically as e.g. `Dimesnions(Val(1), Val(0), Val(0), Val(0), ...)`.

---

<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: [June 15, 2023, 4:28am UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/23 "2023-06-15T04:28:24Z")

</div>

That could be interesting to try. As a package developer, you could test a function for type stability, and if it is, you could switch to using a `TypedDimension` object. As to “Why not use Unitful.jl”, perhaps it’s nicer to have a way of easily switching back and forth between typed and untyped quantities, in case you find your code turns out to be type stable? But maybe I should just try to maintain full API compatibility with Unitful in this case so you could use that instead of duplicating efforts.

In any case, the following PR could be used to implement a `TypedDimension`:  
[Make `AbstractQuantity` and `AbstractDimensions` by MilesCranmer · Pull Request #24 · SymbolicML/DynamicQuantities.jl · GitHub](https://github.com/SymbolicML/DynamicQuantities.jl/pull/24).

For example, here’s a custom quantity with only length/mass/time dimensions:

```julia
julia> using DynamicQuantities

julia> struct MyDimensions{R} <: AbstractDimensions{R}
           length::R
           mass::R
           time::R
           scale::R
       end

julia> struct MyQuantity{T,R} <: AbstractQuantity{T,R}
           value::T
           dimensions::MyDimensions{R}
       end

julia> x = MyQuantity(1.2, time=5//2, scale=3)
1.2 s⁵ᐟ² scale³

julia> x^2
1.44 s⁵ scale⁶

```

All the math and utilities are defined on the abstract types (and use `fieldnames` to find the dimensions), so everything should just work.

The other thing you would need is overload `getproperty` to get dimensions from the type `Val{length}` instead of from the value, and everything else should work.

---

<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: [June 15, 2023, 11:37am UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/24 "2023-06-15T11:37:52Z")

</div>

> [@non-Jedi](#):
>
> The problem is all the functions that dispatch on `::AbstractArray`. Not sure if anyone has written up a proposal for Julia 2.0.

If we can’t use `AbstractArray` as a trait, maybe we could just make `AbstractQuantity` a trait using SimpleTraits.jl instead?

This would look like:

```julia
@traitfn function Base.:+(
    x::Q1, y::Q2
) where {AbstractQuantity{Q1},AbstractQuantity{Q2}}
    dimension(x) != dimension(y) && throw(DimensionError(x, y))
    Q = promote_type(typeof.((x, y))...)
    return Q(ustrip(x) + ustrip(y), dimension(x))
end

```

where we would have

```julia
@traitimpl AbstractQuantity{Q} <- quantity_like(Q)

```

where `quantity_like(Q)` checks that type `Q` defines a `.value` and a `.dimensions`, and that the `typeof(dimensions)` satisfies `AbstractDimensions{X}`.

Then we can create a `QuantityArray{T,R,N,V<:AbstractArray{T,N}} <: AbstractArray{T,N}`, and have

```julia
@traitimpl AbstractQuantity{QuantityArray}

```

So it can dispatch on all the internal calls, while still being treated like an array in other libraries.

I guess this approach is also easier to integrate in other libraries that rely on dispatch to some abstract type.

---

<div class="post-metadata">

### Author: ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)
#### Post date: [June 20, 2023, 1:54am UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/25 "2023-06-20T01:54:09Z")

</div>

Now that we have type-stable dimensional analysis, maybe it’s time for type-stable [Multidimensional Analysis](http://georgehart.com/research/multanal.html).

---

<div class="post-metadata">

### Author: ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)
#### Post date: [June 27, 2023, 7:23pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/26 "2023-06-27T19:23:56Z")

</div>

That is an awesome initiative, thanks for working on it.

I wonder what are your thoughts on scientific types besides the dimensions? The ScientificTypes.jl package has many issues that I am planning to address, but it has the `scitype` trait function that queries if a quantity is “Continuous”, “Count”, … This is very useful in statistical work and often ignored in type systems that absorb data from users.

If we could somehow integrate these worlds into a single world with dimensions and scientific interpretation, that would be great. Any chance a trait like `scitype` could be added to the dynamic quantities? If a quantity is m/s we automatically know it is a Continuous variable for example.

---

<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: [June 28, 2023, 10:14am UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/27 "2023-06-28T10:14:20Z")

</div>

Sure, happy to explore putting it in as an extension! Would you want all quantity objects to be “Continuous”? Or would use of the `mol` be “Count”? What if it is a fraction of a `mol`? I guess if that would be the case, things get tricky, because the physical dimensions are not revealed in the type – so any trait would technically be a union of continuous/count.

I guess it’s also a bit difficult to nail down a single definition because it’s also context-dependent. `s⁻¹` could be a “count” if you are counting discrete things per second. But it could also be the frequency of radiation, which is continuous.

---

<div class="post-metadata">

### Author: ![goretkin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goretkin/32/167_2.png) [@goretkin](https://discourse.julialang.org/u/goretkin)
#### Post date: [June 28, 2023, 10:27am UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/28 "2023-06-28T10:27:00Z")

</div>

Regarding multidimensional analysis: it was a rough attempt years ago, but you might be interested in [GitHub - goretkin/UnitfulLinearAlgebra.jl](https://github.com/goretkin/UnitfulLinearAlgebra.jl)

---

<div class="post-metadata">

### Author: ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)
#### Post date: [June 28, 2023, 12:10pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/29 "2023-06-28T12:10:17Z")

</div>

> [@MilesCranmer](#):
>
> the physical dimensions are not revealed in the type

I think that shouldn’t be a problem, we can take `scitype` of values as well.

---

<div class="post-metadata">

### Author: ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)
#### Post date: [June 28, 2023, 12:12pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/30 "2023-06-28T12:12:47Z")

</div>

> [@MilesCranmer](#):
>
> I guess it’s also a bit difficult to nail down a single definition because it’s also context-dependent. `s⁻¹` could be a “count” if you are counting discrete things per second. But it could also be the frequency of radiation, which is continuous.

That is an interesting observation. I would always assume that rates or frequencies are continuous variables. The count variable here would be the numerator of the rate variable.

---

<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: [June 28, 2023, 12:15pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/31 "2023-06-28T12:15:42Z")

</div>

Actually, good point, it would be continuous either way there.

I suppose if and only if `uamount(::Quantity) > 0` (the dimension corresponding to `mol`) while every other dimension is exactly 0, it would it be a “Count”, while everything else would be Continuous? Or are there other types?

---

<div class="post-metadata">

### Author: ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)
#### Post date: [June 28, 2023, 12:43pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/32 "2023-06-28T12:43:23Z")

</div>

I didn’t explore these ideas enough alongside units, but you can take a look at the type hierarchy here to see if something else pops up in your mind:

> <https://github.com/JuliaAI/ScientificTypesBase.jl/blob/94ca98ba7e92f8db8528fd062b8045acb4c08920/src/ScientificTypesBase.jl#L41-L45>

---

<div class="post-metadata">

### Author: ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)
#### Post date: [June 28, 2023, 1:28pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/33 "2023-06-28T13:28:40Z")

</div>

`mol` is 6e22 molecules and can be considered continuous in most practical cases. C is 6e18 elementary charges, and in most cases can be considered contimuous, too (though in many other cases physics deals with elementary charges). All other units are “even more” continuous, aren’t they?

---

<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: [June 28, 2023, 1:40pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/34 "2023-06-28T13:40:34Z")

</div>

I think I would agree with you. I guess it depends on what these terms are supposed to mean in the package. @juliohm?

Note also that the `Quantity` type has an arbitrary element type (i.e., `Quantity{T}`). So you could store integers in a `Quantity` for example. Maybe if you are counting 1 kg weights, then it makes sense to treat it as a count.

In other words, maybe

```julia
scitype(::Type{Q}) where {Q<:AbstractQuantity{<:Integer}} = Count
scitype(::Type{Q}) where {Q<:AbstractQuantity{<:AbstractFloat}} = Continuous

```

makes the most sense?

* * *

But this is difficult because quantities are defined in the base SI units. So you would not be able to “count” milliseconds if we take this type definition…

---

<div class="post-metadata">

### Author: ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)
#### Post date: [June 28, 2023, 9:19pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/35 "2023-06-28T21:19:50Z")

</div>

> [@MilesCranmer](#):
>
> I think I would agree with you. I guess it depends on what these terms are supposed to mean in the package. @juliohm?

The general use of these scientific types is to decide whether or not we can take means, standard deviations, and other statistics that are only valid with continuous variables. Whenever we get categorical variables such as group labels we cannot perform certain types of analysis and need to do something else instead. If the type system could provide us with this information, we could then automate a bunch of transformations with mixed continuous and categorical variables.

---

<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: [June 28, 2023, 10:02pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/36 "2023-06-28T22:02:04Z")

</div>

In that case it sounds like the most sensible choice is to just forward the value type? i.e.,

```julia
scitype(q::AbstractQuantity) = scitype(ustrip(q))

```

which would make:

```julia
scitype(Quantity{Int}(1.0u"m")) == Count
scitype(Quantity{Float64}(1.0u"m")) == Continuous

```

---

<div class="post-metadata">

### Author: ![longemen3000](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/longemen3000/32/7298_2.png) [@longemen3000](https://discourse.julialang.org/u/longemen3000)
#### Post date: [June 28, 2023, 10:24pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/37 "2023-06-28T22:24:24Z")

</div>

> [@MilesCranmer](#):
>
> I suppose if and only if `uamount(::Quantity) > 0` (the dimension corresponding to `mol`) while every other dimension is exactly 0, it would it be a “Count”, while everything else would be Continuous? Or are there other types?

there are more molecules in a mol than representable `Float64` values in the same exponent (52 bits ≈ 3.5e15 unique numbers)

---

<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: [July 6, 2023, 4:53pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/38 "2023-07-06T16:53:02Z")

</div>

@juliohm I finally got around to making a PR to add `scitype`; please take a look!

> <https://github.com/SymbolicML/DynamicQuantities.jl/pull/31>
>
> @OkonSamuel @ablaom might one of you have some time to perhaps look at this and …let me know if this is the correct way to forward \`scitype\` traits? It's a pretty lightweight interface; it basically just forwards the \`scitype\` call on the value of a physical quantity, while ignore the units.
> 
> The tricky part about this is integration that the units of \`Quantity{...,Dimensions{...}}\` objects are in SI base units, so as an example: you technically could not have a \`Count\` number of femtometers. It would need to be represented as a float, and therefore would be \`Continuous\`. This is a limitation of DynamicQuantities.jl perhaps, although you could create a custom \`AbstractDimensions\` that uses femtometers as a base.
> 
> I'm also opening to simply declaring \`scitype(::AbstractQuantity) = Continuous\` in all cases if that is better semantics, as physical quantities usually are measured continuously. Then again, perhaps someone has a custom \`scitype\` for the values they are working with – and it wouldn't make sense to override their chosen trait.
> 
> Hre are the current tests which illustrate the usage:
> 
> \`\`\`julia
> using DynamicQuantities
> using ScientificTypes
> 
> x = 1.0u"m/s"
> 
> @test scitype(x) \<: Continuous
> @test scitype(\[x\]) \<: AbstractVector{\<:Continuous}
> @test scitype(randn(32) .\* u"m/s") \<: AbstractVector{\<:Continuous}
> 
> \# Converted to integer 1 m/s =\> Count
> @test scitype(Quantity{Int}(x)) \<: Count
> 
> X = (; x=randn(32) .\* u"m/s")
> 
> @test scitype(X) \<: Table{\<:AbstractVector{\<:Continuous}}
> 
> sch = schema(X)
> 
> @test first(sch.names) == :x
> @test first(sch.scitypes) == Continuous
> @test first(sch.types) \<: Quantity{Float64}
> \`\`\`

It just forwards `scitype` to the value. If you want to use integers for quantities then the scitype will be `Count`, which I think sort of feels semantically best. Thoughts?

```julia
using DynamicQuantities
using ScientificTypes

x = 1.0u"m/s"

@test scitype(x) <: Continuous
@test scitype([x]) <: AbstractVector{<:Continuous}
@test scitype(randn(32) .* u"m/s") <: AbstractVector{<:Continuous}

# Converted to integer 1 m/s => Count
@test scitype(Quantity{Int}(x)) <: Count

X = (; x=randn(32) .* u"m/s")

@test scitype(X) <: Table{<:AbstractVector{<:Continuous}}

sch = schema(X)

@test first(sch.names) == :x
@test first(sch.scitypes) == Continuous
@test first(sch.types) <: Quantity{Float64}

```

---

<div class="post-metadata">

### Author: ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)
#### Post date: [July 6, 2023, 7:03pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/39 "2023-07-06T19:03:24Z")

</div>

That is awesome @MilesCranmer , thanks for the quick implementation of the feature request!

---

<div class="post-metadata">

### Author: ![BambOoxX](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bambooxx/32/22179_2.png) [@BambOoxX](https://discourse.julialang.org/u/BambOoxX)
#### Post date: [April 24, 2024, 5:16pm UTC](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963/40 "2024-04-24T17:16:57Z")

</div>

@MilesCranmer Thanks for this nice package. Maybe a silly question, but is there a way to print units using `/` instead or with `^` instead of the small exponents ?

[Previous page](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963.md?page=1)

[Next page](https://discourse.julialang.org/t/ann-dynamicquantities-jl-type-stable-physical-quantities/99963.md?page=3)
