Mixing GPU/CPU in struct definition

Hello,
I use big structs in my code with lots of different vectors of integers and floats like so:

mutable struct Bodies
    a:: Vector{StaticArrays.SVector{3,Float64}}
    b:: Int
    c:: Vector{StaticArrays.SVector{3,Int}}
    d:: Int
    e:: Vector{StaticArrays.SVector{4,Int}}
    f:: Int
    g:: Int
    h:: Vector{Int}
    i:: Vector{Union{ StaticArrays.SVector{3,Int},StaticArrays.SVector{4,Int} }}
    j:: Vector{Vector{Int}}
...

Right now this code is running on the CPU. An older version without structs ran on the GPU and now I want to port this one to be CPU/GPU runnable.
How would you guys recommend I do that? Do I need to have 2 structs definitions, one using CuArrays and one with Vectors or is there a more elegant way to do this?
Thanks a lot!

You can use a single struct definition, with type parameters
https://docs.julialang.org/en/v1/manual/types/#Parametric-Types

1 Like

How does that work with Vectors and CuArrays?
I tried this:

 mutable struct Bodies{V}
           a :: V{StaticArrays.SVector{3,Float64}}
           nPoints :: Int
           c :: V{StaticArrays.SVector{3,Int}}
 end

and it doesn’t work.
Thanks a lot!

Yep, that doesn’t work. The error such a definition throws (I use Dict instead of StaticArray to not need using a package):

julia> struct A{V}
       a::V{Dict{Int,Int}}
       end
ERROR: TypeError: in Type{...} expression, expected UnionAll, got a value of type TypeVar
Stacktrace:
 [1] top-level scope
   @ REPL[1]:1

is a bit cryptic. But it boils down to that types need to be fully defined within the type-def (i.e. a UnionAll and not a TypeVar). This means that you cannot do “type calculations” within the type-def. For instance this is not possible either:

julia> struct B{N}
       a::Array{Int, N+1}
       end
ERROR: MethodError: no method matching +(::TypeVar, ::Int64)
...

What you can do is:

julia> struct D{V<:U where U<:AbstractVector{D} where D<:Dict{Int,Int}}
       a::V
       b::V
       end

julia> D([Dict(1=>1)], [Dict(2=>2)])
D{Vector{Dict{Int64, Int64}}}([Dict(1 => 1)], [Dict(2 => 2)])

or equivalently

julia> struct E{V<:AbstractVector{<:Dict{Int,Int}}}
       a::V
       b::V
       end

julia> E([Dict(1=>1)], [Dict(2=>2)])
E{Vector{Dict{Int64, Int64}}}([Dict(1 => 1)], [Dict(2 => 2)])

However, for your case this approach may not be that useful as the types of your fields all seem a bit different. So, probably you just give each a type parameter (without any fancy & verbose constraints) and be done with it.

Ok, this is getting complicated. I think I might define two structs, as I don’t want the definitions to be super messy.
Thanks a lot!

Yes, that is probably best, as too many type parameters are bad (e.g. really annoying in error messages). You can use something like this to avoid repetition:

julia> for (n,V) in zip([:CPUstuct, :GPUstruct], [:Vector, :CuVector])
       eval(quote
       struct $n
       a::$V{StaticArrays.SVector{3,Float64}}
       c::$V{StaticArrays.SVector{20,Float64}}
       end
       end
       )
       end

(untested)

1 Like