# Defining a rank-N tensor

**URL:** <https://discourse.julialang.org/t/defining-a-rank-n-tensor/56791>\
**Category:** New to Julia\
**Tags:** staticarrays\
**Created:** [March 9, 2021, 9:22am UTC](https://discourse.julialang.org/t/defining-a-rank-n-tensor/56791 "2021-03-09T09:22:13Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![DocDave](https://avatars.discourse-cdn.com/v4/letter/d/eada6e/32.png) [@DocDave](https://discourse.julialang.org/u/DocDave)\
**Post date:** [March 9, 2021, 9:22am UTC](https://discourse.julialang.org/t/defining-a-rank-n-tensor/56791/1 "2021-03-09T09:22:13Z")

</div>

**TL;DR:** I have three main questions:

1. How do I define a `struct` type such that it is limited to one of a few select options?
2. How do I define a struct type such that it can only take integer values?
3. How do I programmatically define a static rank-`N` tensor of specified proportions?

**The longer version:**

I am struggling in defining a `struct` which contains a member variable, `m`, which is a rank-`N` tensor (`typeof(N) <: Integer`) of a fixed size, `L` where `typeof(L) <: Integer`, for all dimensions which contains data of type `T` (limited to `Float32`/`Float64`/`ComplexF32`/`ComplexF64`), i.e. for a placeholding value `X::T`

- `N = 0` results in `m = X` (scalar)
- `N = 1, L = 3` results in `m = [X, X, X]` (vector)
- `N = 2, L = 2` results in `m = [[X, X], [X, X]]` (matrix)  
etc.

In order to achieve this, I must:

1. Limit the data type `T` to one of a select few
2. Define `N` and `L` within the `struct` types such that they can only take integer values (if this is even possible)
3. Programmatically define a rank-`N` tensor

My thought processes so far:

1. I initially attempted to harness the functionality and efficiency provided by `StaticArrays.jl` through e.g.  
`SVector{L, T}(fill(X, L))`  
`SMatrix{L, L, T}(fill(X, (L, L)))`,  
however, these are respectively specific to `N = 1` and `N = 2`, so this is clearly not generalised to `N` dimensions.
2. I found that generic `SArray`s can be generalised to `N` dimensions, e.g.  
`SArray{Tuple{L}, T}(fill(X, L))`  
`SArray{Tuple{L, L}, T}(fill(X, (L, L)))`,  
However, this still requires specification with the correct number of arguments.
3. My guess in resolving this shortcoming would be to utilise the `repeat()` function somehow, however I am not seeing how to do so.

My usage of structs in julia is pretty rudimentary, but ultimately I assume the `struct` would look akin to:

```julia
struct My_Struct{T, N, L} where T \in [Float32, Float64, ComplexF32, ComplexF64], typeof(N) <: Integer, typeof(L) <: Integer
    m::SArray{Tuple{repeat([X], L)}, T}
end

```

Any help would be greatly appreciated!

---

<div class="post-metadata">

**Author:** ![sijo](https://avatars.discourse-cdn.com/v4/letter/s/da6949/32.png) [@sijo](https://discourse.julialang.org/u/sijo)\
**Post date:** [March 9, 2021, 11:34am UTC](https://discourse.julialang.org/t/defining-a-rank-n-tensor/56791/2 "2021-03-09T11:34:48Z")

</div>

I think the essence of what you want can be done like this:

```julia
using StaticArrays

struct MyStruct{T, N, L}
    m::SArray{NTuple{N, L}, T}
end

# Test
data = reshape(1:8, 2, 2, 2) .|> Float64
sarray = SArray{NTuple{3,2}}(data)

julia> my_struct = MyStruct(sarray)
MyStruct{Float64, 3, 2}([1.0 3.0; 2.0 4.0]

[5.0 7.0; 6.0 8.0])

julia> typeof(my_struct.m)
SArray{Tuple{2, 2, 2}, Float64, 3, 8}

```

`NTuple{N,L}` does what you where getting at with `repeat`.

You can easily add the constraint on `T` with `Union`:

```julia
struct MyStruct2{T<:Union{AbstractFloat, ComplexF32, ComplexF64}, N, L} 
    m::SArray{NTuple{N, L}, T}
end

```

though it makes it less flexible (and less readable in my opinion) so I’m not sure the constraint is a good idea.

I don’t think you can add “integer” constraints on the `L` and `N` parameters (see discussion at [https://github.com/JuliaLang/julia/issues/9580](https://github.com/JuliaLang/julia/issues/9580)).

---

<div class="post-metadata">

**Author:** ![thisrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thisrod/32/10642_2.png) [@thisrod](https://discourse.julialang.org/u/thisrod)\
**Post date:** [March 10, 2021, 1:14am UTC](https://discourse.julialang.org/t/defining-a-rank-n-tensor/56791/3 "2021-03-10T01:14:43Z")

</div>

> [@sijo](#):
>
> I don’t think you can add “integer” constraints on the `L` and `N` parameters

The idiom for that is a guarded inner constructor:

```
struct MyStruct{T, N, L}
    m::Array{T}
    
    function MyStruct{T, N, L}(m) where {T,N,L}
        @assert N isa Integer
        @assert L isa Integer
        new{T, N, L}(m)
    end
end

julia> MyStruct{Float64, 2, 3}(rand(3,3))
...

julia> MyStruct{Float64, 2.0, 3}(rand(3,3))
ERROR: AssertionError: N isa Integer

```

---

<div class="post-metadata">

**Author:** ![DocDave](https://avatars.discourse-cdn.com/v4/letter/d/eada6e/32.png) [@DocDave](https://discourse.julialang.org/u/DocDave)\
**Post date:** [March 10, 2021, 10:07am UTC](https://discourse.julialang.org/t/defining-a-rank-n-tensor/56791/4 "2021-03-10T10:07:27Z")

</div>

@sijo Many thanks, that appears to be what I am looking for, and at least I now better understand how `Union` can be used! Regarding flexibility, I don’t envisage requiring further options than those specified in my OP, but maybe those are famous last words 🙂

@thisrod Is there a way to non-explicitly implement those assertions into every constructor (I will be creating various constructors as I suspect users will desire the ability to fall back on default values), or is it a case of concision vs. rigour?

---

<div class="post-metadata">

**Author:** ![thisrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thisrod/32/10642_2.png) [@thisrod](https://discourse.julialang.org/u/thisrod)\
**Post date:** [March 11, 2021, 3:50am UTC](https://discourse.julialang.org/t/defining-a-rank-n-tensor/56791/5 "2021-03-11T03:50:34Z")

</div>

> [@DocDave](#):
>
> Is there a way to non-explicitly implement those assertions into every constructor (I will be creating various constructors as I suspect users will desire the ability to fall back on default values), or is it a case of concision vs. rigour?

You would normally define the convenience methods as [outer constructors](https://docs.julialang.org/en/v1/manual/constructors/#man-outer-constructor-methods), so this happens automatically. The outer constructors are ordinary Julia methods, which call a privileged [inner constructor](https://docs.julialang.org/en/v1/manual/constructors/#man-inner-constructor-methods) method, which does the checks and creates the `MyStruct`.

Every value of type `MyStruct` was created when an inner constructor returned it. If you explicitly define an inner constructor in the type definition, Julia doesn’t define any others, and there are no `MyStructs` except for the ones returned by your constructor. If you don’t, Julia defines the obvious inner constructors by default.
