Creating a struct with 1D length using unitful.jl

I’m trying to build a data type to hold a measurements of a quantity. I know that this quantity will be a length, but don’t know a priori what unit that length will be. I’m having a bit of trouble coming up with syntax to guarantee that heights will be a length using the Unitful types.

using Unitful

struct Measurements
    heights::Array{Unitful.Quantity,1} # this line
end

Is there a better way to do this? Thanks for the help!

Perhaps like this?

julia> struct Measurements{T <: Quantity{<:Any, Unitful.𝐋}}
           heights::Vector{T}
       end

julia> Measurements([1u"m", 2u"cm"])
Measurements{Quantity{Rational{Int64},𝐋,Unitful.FreeUnits{(m,),𝐋,nothing}}}(Quantity{Rational{Int64},𝐋,Unitful.FreeUnits{(m,),𝐋,nothing}}[1//1 m, 1//50 m])

julia> Measurements([1u"m", 2u"s"])
ERROR: MethodError: no method matching Measurements(::Array{Quantity{Int64,D,U} where U where D,1})

Edit: If you don’t want the type parameter you can use

julia> struct Measurements
           heights::Vector{<: Quantity{<:Any, Unitful.𝐋}}
       end
3 Likes

thanks, sorry for the obvious question

You can also just specify a specific length unit, and on insertion convert from the given length to the length required by the vector. Is there a reason you want to keep track of what unit was originally provided?

1 Like

This may actually be a better approach – there is no need to track the original unit and it would be helpful to convert by default to a unit (say feet)

1 Like

I usually use the following when I’ve build functionality around Unitful.jl. There are many different aliases for common dimensions.

struct Measurements
    heights::Vector{<:Unitful.Length}
end
1 Like

Cool, if one wanted to convert to feet by default is a constructor the place to do it? Something like

struct Measurements
    heights::Vector{<: Unitful.Length}
    function Measurements(x::Vector{<: Unitful.Length})
       x_conv = [Unitful.uconvert(Unitful.u"ft", xi) for xi in x]
       new(x_conv)
    end
end

You can use upreferred to transform to SI units

I think this is the intended way, and it works better for performance since the vector is concretely typed:

julia> using Unitful

julia> a = Vector{typeof(1.0u"μm")}()
push!0-element Array{Quantity{Float64,𝐋,Unitful.FreeUnits{((μm,),𝐋,nothing}},1}

julia> push!(a,1u"m")
push1-element Array{Quantity{Float64,𝐋,Unitful.FreeUnits{(μm,),𝐋,nothing}},1}:
 1.0e6 μm

julia> push!(a,7u"cm")
2-element Array{Quantity{Float64,𝐋,Unitful.FreeUnits{(μm,),𝐋,nothing}},1}:
   1.0e6 μm
 70000.0 μm

To make your struct concrete, but also more flexible, I’d do something like this:

#+begin_src julia
using Unitful
struct Measurements{T <: Unitful.Length}
    heights::Vector{T}
    Measurements(x::Vector{<: Unitful.Length}) = [Unitful.uconvert(Unitful.u"ft", xi) for xi in x]
end

Measurements([1u"cm", 1.0u"mm", 1e10u"fm"])

#+end_src

#+RESULTS:
: 3-element Array{Quantity{Float64,𝐋,Unitful.FreeUnits{(ft,),𝐋,nothing}},1}:
:    0.03280839895013123 ft
:  0.0032808398950131233 ft
:   3.280839895013124e-5 ft

If you want to change the preferred units for your use case, see https://painterqubits.github.io/Unitful.jl/stable/conversion/#Unitful.preferunits

So for example, to use feet instead of meters, you could do

using Unitful
Unitful.preferredunits(u"ft")

upreferred(2u"m")
# 2500//381 ft