How can you define arrays of increasing dimension in a composite type when they have a simple form such as the following?

```
struct compound{N} # N is the number of spatial dimensions of the field
scalar_field :: Array{Float64,N}
vector_field :: Array{Float64,N+1}
tensor_field :: Array{Float64,N+2}
end
```

But this throws the error

**MethodError: no method matching +(::TypeVar, ::Int64)**

I guess I could make an Array of Arrays, but I would rather keep them flat like this.

I think you have to settle for three type parameters and an assertion:

```
struct Compound{N, M, P}
scalar_field::Array{Float64, N}
vector_field::Array{Float64, M}
tensor_field::Array{Float64, P}
function Compound(s, v, t)
# alternatively throw a more appropriate error type like
# a DimensionMismatch or MethodError
@assert ndims(s) == ndims(v)-1 == ndims(t)-2 "error message about dimension"
N = ndims(s)
new{N, N+1, N+2}(s, v, t)
end
end
```

2 Likes

I donâ€™t know of any better way than parameterizing `Compound`

with three independent parameters (e.g. `N`

, `N1`

, `N2`

) and enforce the relation between them in an inner constructor.

Something along the lines of:

```
julia> struct Compound{N, N1, N2}
scalar_field :: Array{Float64,N} # N is the number of spatial dimensions of the field
vector_field :: Array{Float64,N1} # N1 should be N+1
tensor_field :: Array{Float64,N2} # N2 should be N+2
function Compound(N)
new{N, N+1, N+2}()
end
end
julia> Compound(5)
Compound{5,6,7}(#undef, #undef, #undef)
```

1 Like

Drats! Thatâ€™s what Iâ€™m doing now and it is soooo ugly.

```
struct Compound{N}
scalar_field :: Array{Float64,N}
vector_field :: Array{SVector{1,Float},N}
tensor_field :: Array{SVector{2,Float},N}
end
```

Maybe ?

Yeah, the is the *array of arrays* that I was considering above. Is there a performance hit for nesting arrays like this?

You could try with meta programming

```
abstract type AbstractCompound{N} end
for N = 1:4
name = Symbol("Compound$N")
@eval begin
struct $name <: AbstractCompound{$(N)} # N is the number of spatial dimensions of the field
scalar_field :: Array{Float64,$(N)}
vector_field :: Array{Float64,$(N+1)}
tensor_field :: Array{Float64,$(N+2)}
end
end
end
julia> c = Compound1(zeros(1),zeros(1,1),zeros(1,1,1))
Compound1([0.0], [0.0], [0.0])
julia> c.tensor_field
1Ă—1Ă—1 Array{Float64, 3}:
[:, :, 1] =
0.0
```

1 Like

Oops ! Too fast reading (although not fast enough to provide the first solution )

I think that you can reshape your arrays as flat arrays.

If you want to minimize the performance risks maybe you may adopt a SoA strategy (but the client code is likely to be â€śuglierâ€ť)

I am thinking about the best strategy and I confess that at this point nothingâ€™s really convince meâ€¦

1 Like

Neat! This would be much nicer when writing thing like `foo(a::AbstractCompound{N}) where N`

.

Would it be possible to overload the internal constructor? I often have code like `b = Compound(a.scalar_field)`

and now it looks like I would need to check if I needed `Compound2`

, `Compound3`

,â€¦

Thanks. Thatâ€™s my current approach.

You can solve this problem the same way, with meta programming. The only thing to keep in mind is to interpolate the `N`

variable into the evaluated expression with `$(N)`

.

2 Likes

This seems to work well!

```
abstract type AbstractCompound{N} end
for N = 1:4
name = Symbol("Compound$N")
@eval begin
struct $name <: AbstractCompound{$(N)} # N is the number of spatial dimensions of the field
scalar_field :: Array{Float64,$(N)}
vector_field :: Array{Float64,$(N+1)}
tensor_field :: Array{Float64,$(N+2)}
end
Compound(s::AbstractArray{Float64,$(N)},a...) = $name(s,a...)
end
end
Compound(N) = Compound(rand(N...),zeros(N...,length(N)),ones(N...,length(N),length(N)))
julia> Compound((2,2)).vector_field
2Ă—2Ă—2 Array{Float64,3}:
[:, :, 1] =
0.0 0.0
0.0 0.0
[:, :, 2] =
0.0 0.0
0.0 0.0
```

2 Likes

Iâ€™m glad you found a solution that works for you! I do have some more thoughts reading through all the replies though.

What aspect is ugly? The definition itself, the REPL printout `Compound{5, 6, 7}`

, or something else? If it is the definition, then of course youâ€™re out of luck with this method. If itâ€™s the printout or having to refer to a type with 3 parameters elsewhere in the code, then there are simple enough remedies.

If the N is necessary for the method, you would still need to write it this way I think. If not, you can omit it either way. Note also that you can write parameterized types with only some of the parameters defined. The parameters are interpreted in order.

```
julia> AbstractCompound == AbstractCompound{N} where N
true
julia> Compound{1}
Compound{1,M,P} where P where M
julia> Array{Int} # same idea as this
Array{Int64,N} where N
```

If for whatever reason you need the type to be totally concrete for some usage but donâ€™t want to refer to it with all 3 parameters, you could define something like `compoundtype(n) = Compound{n, n+1, n+2}`

as sugar for referring to the appropriate type. You could also define aliases like `const Compound1 = Compound{1,2,3}`

which is similar to what youâ€™re doing now.

Finally, the constructor you defined for `Compound(N)`

would work (as written) just as well for a type with 3 parameters under the hood.

All of this is not to say that metaprogramming and having multiple types for Compound1, Compound2, etc. is not also a perfectly good way to go!

2 Likes

Nice! I didnâ€™t realize you could drop some of the parameters. That does clean up the usage a good deal.

I still prefer strictly defining `n,n+1,n+2`

if possible since there really is only one parameter for the type, not three.

Point of fact - I ended up doing this since it was the closest to what I had already, so why reinvent the wheel?

1 Like