Changing the basic type of a Unitful quantity

I’m developing some electromagnetic code and want to use Unitful.jl thoroughly to make sure that data structures and algorithms are physically consistent.

One feature I would like to have is the ability to quickly set up structures using a basic floating-point data type with the appropriate bitsize, e.g., Float32 vs Float64. However, I struggle to understand how to make the Julia type system to do what I want.

I can explain the problem using a very simple 2D vector data structure as an example:

import Unitful: Length, m

struct Vec{T, L <: Length{T}}
    x::L
    y::L
end

This type works well with both Float32 and Float64 numbers:

julia> Vec(1.0m, 2.0m)
Vec{Float64, Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}(1.0 m, 2.0 m)
julia> Vec(1.0f0m, 2.0f0m)
Vec{Float32, Quantity{Float32, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}(1.0f0 m, 2.0f0 m)

Suppose that now I want to define an outer constructor where I pass the Float32/Float64/ComplexF32/… type explicitly, making sure that the constructor properly converts the types. This could be useful if e.g. I want to be sure to use a wide data type in some accuracy-sensitive algorithm.

In this particular example, I would like to write Vec{Float64}(x, y) and keep the lengths x and y stored as Float64 lengths even if the values passed as x and y are Float32.

I struggle to understand how to code it, as the following attempt does not work:

julia> Vec{T}(x::L, y::L) where {T, L <: Length} = Vec{T, L}(T(x), T(y))

julia> Vec{Float64}(1.0f0m, 2.0f0m)
ERROR: TypeError: in Vec, in L, expected L<:(Union{Quantity{Float64, 𝐋, U}, Unitful.Level{L, S, Quantity{Float64, 𝐋, U}} where {L, S}} where U), got Type{Quantity{Float32, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}
Stacktrace:
 [1] (Vec{…} where L<:(Union{…} where U))(x::Quantity{…}, y::Quantity{…})
   @ Main ./REPL[10]:1
 [2] top-level scope
   @ REPL[15]:1
Some type information was truncated. Use `show(err)` to see complete types.

I understand that the problem lies in the fact that when I write Vec{T, L}, the type L is still based on Float32. But I fail to see an easy way to β€œpromote” type L so that it keeps using the same measurement units (m, ft, inch…) as the input parameters while switching the basic type from Float32 to Float64.

Can anybody provide me a few hints, please?

Edit: Fix a typo.

Hi!

It seems that convert(Quantity{T}, x) instead of T(x) does what you need.

1 Like

Fantastic! With this implementation

function Vec{T}(x::L, y::L) where {T, L <: Length}
    newx = convert(Quantity{T}, x)
    newy = convert(Quantity{T}, y)
    return Vec{T, typeof(newx)}(newx, newy)
end

this works as expected:

julia> Vec{Float64}(1.0f0m, 2.0f0m)
Vec{Float64, Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}(1.0 m, 2.0 m)

Thank you very much!