How to use Parametric Structs with Unitful

I am trying to create a package that solves the kinematics of a closed mechanism. The mechanism type consists of a list of link types that I have also defined parametricly:

``````abstract type Link end

r::T # Vector Length
end

struct Mechanism
n::Integer
variables::Tuple{Integer, Integer} #index of variable links (2)
end
``````

Using Unitful I tried to create a 4-bar Mechanism:

``````bar1 = BarLink{Quantity}(10u"inch", Float64(π)u"rad", 0u"m/m", 0u"m/m") # line throws error

fourbar = Mechanism(4, 2, (3, 4), [bar1; bar2; bar3; bar4])
``````

The error thrown from the initialization of `bar1`:

``````MethodError: no method matching Unitful.Quantity(::Int64)
``````

For reference, I have similar code that works for other subtypes of Number:

``````bar1 = BarLink{Real}(10, π, 0, 0)

fourbar = Mechanism(4, 2, (3, 4), [bar1; bar2; bar3; bar4])
``````

and with SymPy:

``````@syms r1 r2 r3 r4 θ1 θ2 θ3 θ4 ω2 α2

bar1 = BarLink{Sym}(r1, θ1, 0, 0)
bar2 = BarLink{Sym}(r2, θ2, 0, 0)
bar3 = BarLink{Sym}(r3, θ3, 0, 0)
bar4 = BarLink{Sym}(r4, θ4, 0, 0)

fourbar = Mechanism(4, 2, (3,4), [bar1; bar2; bar3; bar4])
``````

I have no idea why a method error is thrown, since Quantity is a subtype of Number, and also is abstract enough to contain different concrete subtypes of Quantity, shouldn’t the constructor create the datatype?

There are some subtleties with Unitful. The unit `m/m` eagerly cancels the `m` to become dimensionless and actually a scalar Int. The following is something put together to avoid this:

``````# using Unitful and definition above

const mpm1 = Unitful.FreeUnits{(Unitful.Unit{:Meter, Unitful.𝐋}(0, 1),
Unitful.Unit{:Meter, Unitful.𝐋}(0, -1)), NoDims, nothing}
const mpm = Quantity{Int64, NoDims, mpm1}(1)

fourbar = Mechanism(4, 2, (3, 4), [bar1; bar2; bar3; bar4])
``````

I’ll be happy to see more idiomatic ways of creating `m/m` unit.

1 Like

If you are looking for performance, then “Avoid fields with abstract type”: Performance Tips · The Julia Language
(Quantity, Integer, and Link are all abstract types)[1]

I.e, if you want to keep the types parametric, it should be sth like

``````abstract type Link end

r::R # Vector Length
end

n::Int
variables::Tuple{Int,Int} #index of variable links (2)
end
``````

(Note e.g. the `Int` (== `Int64`) instead of `Integer`: concrete vs abstract type. You could also annotate those fields `::I` and add `,I` or `,I<:Integer` in the parameter clause)

1. Note that this only applies to annotations of struct fields. Function argument annotations can still be happily abstract (or not given at all) ↩︎

You don’t have to specify all those new type params on creation btw. They get filled in automatically. I.e:

``````bar1 = BarLink(10u"inch", Float64(π)u"rad", 0u"rad/s", 0u"rad/s/s")
bar1 = BarLink(10, π, 0, 0)
bar1 = BarLink(r1, θ1, 0, 0)

fourbar = Mechanism(4, 2, (3, 4), [bar1; bar2; bar3; bar4])
``````