Use unitful unit to a variable

How can I attach a unit to a variable ?

e.g.

julia> using Unitful

julia> 25u"km" #works
25 km

julia> a = 25; # define a variable

julia> #would expect something like that or similar to work
julia> au = @u_str(a, "km")
ERROR: LoadError: MethodError: no method matching var"@u_str"(::LineNumberNode, ::Module, ::Symbol, ::String)
Closest candidates are:
  var"@u_str"(::LineNumberNode, ::Module, ::Any) at ~/.julia/packages/Unitful/ApCuY/src/user.jl:629
in expression starting at REPL[6]:1

For now I solve this with eval, but it doesn’t feel right:

julia> au = eval(:a)u"km"
25 km

What’s the way to go here ?

julia> a = 25u"km"
25 km

julia> a
25 km

this?

Or this:

julia> b = 25 * oneunit(a)
25 km

In some sense, you cannot “attach” a unit to variable, because a variable with a unit is in some sense just a type of number. Is like if you could attach the property of being a Complex{Float64} to a Float64. You cannot, you have to create a new variable for that.

1 Like

I want to take the value inside of a variable and attach it to a type.
I don’t want to use a literal like 25 again.
E.g., let’s say I derived programmatically a long Int[] array and I want to attach u"km" to it.
Can I do it without eval ?

julia> very_complex_func() = [1,2,3,4,5]
very_complex_func (generic function with 1 method)

julia> ar = very_complex_func();

julia> aru = eval(ar)u"km"
5-element Vector{Quantity{Int64, 𝐋, Unitful.FreeUnits{(km,), 𝐋, nothing}}}:
 1 km
 2 km
 3 km
 4 km
 5 km

I don’t quite agree.
I can initiate a Complex and a Float64 with a variable and not always a literal is needed.
Also I can “attach” a Float64 value to a Complex{Float64} like the following:

julia> num = 3;

julia> a = Float64(num);

julia> ac = Complex(a,a)
3.0 + 3.0im

Instead to initialize a Unitful type, it looks like I always need a literal.

You can multiply by the unit, for example. But I don’t know if this is what you don’t want to do.

julia> using Unitful
julia> a = [1, 2, 3]
julia> b = 1u"km".*a
3-element Vector{Quantity{Int64, 𝐋, Unitful.FreeUnits{(km,), 𝐋, nothing}}}:
 1 km
 2 km
 3 km

What you were trying to do with @u_str is also possible, but there is no need to introduce the a intro the macro call.

julia> a*@u_str("km")
3-element Vector{Quantity{Int64, 𝐋, Unitful.FreeUnits{(km,), 𝐋, nothing}}}:
 1 km
 2 km
 3 km

What happens is that the way of “attaching” a unit to a value, even in the example of a = 25u"km" is doing a product between 25 and the thing that comes back from u"km"

2 Likes

I don´t know if Unitful has a specific function for that. But you cannot “attach” the units to such array, because the array is of immutable values, and the unitful values are a different type of animal.

Thus, you can do, for example:

julia> x = [1,2,3,4,5];

julia> x .* 1u"km"
5-element Vector{Quantity{Int64, 𝐋, Unitful.FreeUnits{(km,), 𝐋, nothing}}}:
 1 km
 2 km
 3 km
 4 km
 5 km

I’m not sure if there is a convert(...) type of function in Unitful for doing that with another syntax. But anyway you need to define a new variable.

(I think that if the purpose is to propagate units that are attached to some input variable, the most useful pattern is something like:

julia> a = 1u"km"
1 km

julia> x = [1,2];

julia> y = x .* oneunit(a)
2-element Vector{Quantity{Int64, 𝐋, Unitful.FreeUnits{(km,), 𝐋, nothing}}}:
 1 km
 2 km
1 Like

Yeah, the multiplication with 1u"km" again works (probably better than the eval) but again it seems to me like a “hack”. Anyway, I better get used to it. :smiley: Thanks!

It is not a hack, it is actually the usual way:

You can see it with:

julia> @which 25u"km"
*(x::Number, y::Unitful.Units, z::Unitful.Units...) in Unitful at /Users/arreyes/.julia/packages/Unitful/ApCuY/src/quantities.jl:26

This actually calls the product of 25 and u"km". This product is defined at unitful. So it is not more esoteric than that.

3 Likes

If you think in terms of actual units, it is not a hack, it is what actually make sense. A dimensionless quantity does not assume dimensions, but it may be multiplied by a quantity with dimensions, and the result has the dimensions of that quantity.

It is not that you can “fix” the fact that you forgot to write the units of a quantity, because the types are different. For instance, you cannot do this:

julia> x = [ 1, 2 ];

julia> x[1] = 1u"km"
ERROR: DimensionError:  and km are not dimensionally compatible.

Because x is a container of unitless (Int64) numbers only.

You can, however, do this:

julia> x = [1.0, 2.0]u"km"
2-element Vector{Quantity{Float64, 𝐋, Unitful.FreeUnits{(km,), 𝐋, nothing}}}:
 1.0 km
 2.0 km

julia> x[1] = 1.0u"m"
1.0 m

julia> x
2-element Vector{Quantity{Float64, 𝐋, Unitful.FreeUnits{(km,), 𝐋, nothing}}}:
 0.001 km
   2.0 km

since how it makes sense to convert the meter to the km unit which the vector carries.

*you could do this:

julia> x = Number[1.0, 2.0];

julia> x[1] = 1.0u"km"
1.0 km

julia> x
2-element Vector{Number}:
 1.0 km
      2.0

but that is only because that vector can contain anything that is a subtype of number, and there is no relation between the values before and after the mutation - and this is very bad for performance.

2 Likes