Creating many large arrays within structs

Suppose I have an (immutable) struct that holds many large arrays. Not all of these arrays need to exist, however, it just depends on the user-specified problem setup. So I defined my struct like this:

@with_kw struct mystruct
    a::Union{Nothing,Matrix{Float64}} = nothing
    b::Union{Nothing,Matrix{Float64}} = nothing
    c::Union{Nothing,Matrix{Float64}} = nothing
    d::Union{Nothing,Matrix{Float64}} = nothing
end

Now I have some methods that initialize this struct differently depending on what the user specifies:

function init_my_struct(::Val{1})
    return mystruct(a=zeros(500,500),b=zeros(500,500))
end

function init_my_struct(::Val{2})
    return mystruct(c=zeros(500,500),d=zeros(500,500))
end

function init_my_struct(::Val{3})
    return mystruct(a=zeros(500,500),b=zeros(500,500),c=zeros(500,500),d=zeros(500,500)),
end

(I’m dispatching by value just to highlight the three different cases).

If you notice, the third case is really just a combination of the first two cases. In this particular example, it is easy to manually rewrite out each case. But in my actual code, I have many cases for dozens of arrays…

Is there an efficient way to pass blocks of parameters like this? Maybe via metaprogramming?

I thought about writing another function (one for each case) which simply returns a vector of all the arrays for that particular case, then I could splat them as needed. However, this seems extremely inefficient as that would effectively allocate each array twice

I appreciate the help!

Would named tuples work for you?

julia> ab = (a=zeros(2, 2), b=zeros(2, 2))
(a = [0.0 0.0; 0.0 0.0], b = [0.0 0.0; 0.0 0.0])

julia> cd = (c=zeros(2, 2), d=zeros(1, 1))
(c = [0.0 0.0; 0.0 0.0], d = [0.0;;])

julia> abcd = (; ab..., cd...)
(a = [0.0 0.0; 0.0 0.0], b = [0.0 0.0; 0.0 0.0], c = [0.0 0.0; 0.0 0.0], d = [0.0;;])

Here, the splatting allocates a new tuple, but does not allocate the arrays inside the fields

julia> ab.a[1, 2] = 1
1

julia> abcd
(a = [0.0 1.0; 0.0 0.0], b = [0.0 0.0; 0.0 0.0], c = [0.0 0.0; 0.0 0.0], d = [0.0;;])

and can be easily passed to your struct as well:

julia> mystruct(; ab...)
mystruct
  a: Array{Float64}((2, 2)) [0.0 1.0; 0.0 0.0]
  b: Array{Float64}((2, 2)) [0.0 0.0; 0.0 0.0]
  c: Nothing nothing
  d: Nothing nothing
2 Likes

What is the advantage of immuatbility of your struct here ? I think it is not necessary, but of course I don’t know your context.

If you can tolerate mutability, you could make your entries unions like e.g. here, so that you can maintain type stability.

Here, the splatting allocates a new tuple, but does not allocate the arrays inside the fields

Hey, this is clever! This works well, thank you!

What is the advantage of immuatbility of your struct here ?

These arrays are going through many loops over and over. I’m passing the struct containing all these arrays all over the place. I assumed it would help performance if I kept it immutable.

If you can tolerate mutability, you could make your entries unions like e.g. here , so that you can maintain type stability.

Is this not what I’m already doing above? What does mutability buy you here?

@with_kw struct mystruct
    a::Union{Nothing,Matrix{Float64}} = nothing
    b::Union{Nothing,Matrix{Float64}} = nothing
    c::Union{Nothing,Matrix{Float64}} = nothing
    d::Union{Nothing,Matrix{Float64}} = nothing
end
1 Like