Require all arrays in struct to be of same kind and type

I have a struct that I would like to consist of only Array or CuArray, but I would like a mix of different kinds to throw. I can enforce uniform types by using the {T}, but how do I define my struct to make the vel_mix construction throw an error?

using CUDA

struct Velocity{T}
    u::AbstractArray{T, 2}
    v::AbstractArray{T, 2}
end

u = zeros(3, 3); v = zeros(3, 3)

vel_cpu = Velocity(u, v) # OK
vel_gpu = Velocity(CuArray(u), CuArray(v)) # OK
vel_mix = Velocity(CuArray(u), v) # I would like this one to throw.
julia> struct Velocity{T, U<:AbstractArray{T}}
           u::U
           v::U
       end

julia> Velocity([1,2], 1:2)
ERROR: MethodError: no method matching Velocity(::Vector{Int64}, ::UnitRange{Int64})
Closest candidates are:
  Velocity(::U, ::U) where {T, U<:(AbstractArray{T})} at REPL[1]:2
Stacktrace:
 [1] top-level scope
   @ REPL[2]:1

julia> Velocity([1,2], [1,2])
Velocity{Int64, Vector{Int64}}([1, 2], [1, 2])

julia> Velocity(1:2, 1:2)
Velocity{Int64, UnitRange{Int64}}(1:2, 1:2)
5 Likes

I think you can even drop the first type parameter:

click me for code
julia> struct Velocity{U<:AbstractArray}
           u::U
           v::U
       end

julia> Velocity(1:2, 1.0:2.0)
ERROR: MethodError: no method matching Velocity(::UnitRange{Int64}, ::StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64})
Closest candidates are:
  Velocity(::U, ::U) where U<:AbstractArray at REPL[1]:2
Stacktrace:
 [1] top-level scope
   @ REPL[2]:1

julia> Velocity(1:2, [1,2])
ERROR: MethodError: no method matching Velocity(::UnitRange{Int64}, ::Vector{Int64})
Closest candidates are:
  Velocity(::U, ::U) where U<:AbstractArray at REPL[1]:2
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1

julia> Velocity(1:2, 1:2)
Velocity{UnitRange{Int64}}(1:2, 1:2)

julia> Velocity([1,2], [1,2])
Velocity{Vector{Int64}}([1, 2], [1, 2])

julia> Velocity([1,2], [1.0,2.0])
ERROR: MethodError: no method matching Velocity(::Vector{Int64}, ::Vector{Float64})
Closest candidates are:
  Velocity(::U, ::U) where U<:AbstractArray at REPL[1]:2
Stacktrace:
 [1] top-level scope
   @ REPL[6]:1

though you are going to need at least U <: AbstractArray{T} where T if you want to use T as well for dispatch or use it in the struct.

2 Likes

And what if I wanted:

struct Velocity{T}
    u::AbstractArray{T, 2}
    v::AbstractArray{T, 2}
    z::AbstractArray{T, 1}
end

where the last element must be of the same kind and type, but has one dimension less?

I may be wrong but I think you’ll need to use a constructor to enforce the dimensions. I don’t think you can express the relationship of “one dimension less” (because you can’t do math on the type) in the type system (but please someone prove me wrong if it is possible).

1 Like

You probably don’t want this, because then the u field is abstractly typed.

Better to have something like:

struct Velocity{A<:AbstractMatrix}
    u::A
    v::A
end
7 Likes

My question wasn’t well formulated. What I meant to ask was: what if I mix different dimension sizes, can I define the struct such that all arrays are of the same kind, irrespective of their nmber of dimensions, or do I need a constructor for that?

For instance

struct Velocity{T, U2 <: AbstractArray{T, 2}, U1 <: AbstractArray{T, 1}}
    u::U2
    v::U2
    z::U1
end

would not solve it, because it allows U1 to be an Array and U2 a CuArray.

I don’t think so.