SymmetricTensors.jl provides the `SymmetricTensors{T, N}`

type used to store fully symmetric tensors in more efficient way,
without most of redundant repetitions. It uses blocks of `Array{T, N}`

stored in `Union{Array{Float,N}, Nothing}`

structure.
Repeated blocks are replaced by `Void`

. The module introduces `SymmetricTensors{T, N}`

type and some basic operations on this type.
As of 01/01/2017 @kdomino is the lead maintainer of this package.

Within Julia, just use run

`pkg> add SymmetricTensors`

to install the files. Julia 1.0 or later is required.

```
julia> data = ones(4,4);
julia> SymmetricTensor{Float64,2}(Union{Nothing, Array{Float64,2}}[[1.0 1.0; 1.0 1.0] [1.0 1.0; 1.0 1.0]; nothing [1.0 1.0; 1.0 1.0]], 2, 2, 4, true)
```

From `Array{T, N}`

to `SymmetricTensors{T, N}`

`julia> SymmetricTensors(data::Array{T, N}, bls::Int = 2)`

where bls is the size of a block. It is a parameter affecting the compuational speed of cumulants. The block size must fulfill `bls ∈ {1, 2,..., dats}`

where `dats = size(data, 1) = ... = size(data, N)`

otherwise error is risen.

```
julia> data = ones(4,4);
julia> convert(SymmetricTensor, data, 2)
SymmetricTensor{Float64,2}(Union{Nothing, Array{Float64,2}}[[1.0 1.0; 1.0 1.0] [1.0 1.0; 1.0 1.0]; nothing [1.0 1.0; 1.0 1.0]], 2, 2, 4, true)
```

From `SymmetricTensors{T, N}`

to `Array{T, N}`

`julia> Array(st::SymmetricTensors{T, N})`

Wrong block size:

```
julia> SymmetricTensor(ones(4,4), 5)
ERROR: DimensionMismatch("bad block size 5 > 4")
```

`frame::ArrayNArrays{T,N}`

: stores data, where`ArrayNArrays{T,N} = Array{Union{Array{T, N}, Nothing}, N}`

`bls::Int`

: the size of ordinary block (the same in each direction),`bln::Int`

: maximal number of blocks in each direction,`dats::Int`

: the size of data stored (the same in each direction),`sqr::Bool`

: if all blocks are squares (N-squares).

Suppose we have `N = 2`

and `dats = 6`

and `bls = 3`

hence data are symmetric matrix of size `6 x 6`

. Data are stored in the form:

```
|B11 B12 |
|null B22 |
```

here `bls = 2`

and size of `B11`

, `B12`

, and `B22`

are `3 x 3`

. Bear in mind, that `B11`

and `B22`

his to be symmetric. As `B12`

(the last block) is square, `sqr = True`

.

Suppose now `N = 2`

and `dats = 5`

and `bls = 3`

hence data are symmetric matrix of size `5 x 5`

. Data are stored in similar form:

```
|B11 B12 |
|null B22 |
```

here `bls = 2`

and size of `B11`

is `3 x 3`

, but size of `B12`

is `2 x 3`

, and size `B22`

is `2 x 2 `

. Again `B11`

and `B22`

his to be symmetric. As `B12`

(the last block) is not square, `sqr = False`

.

For `N = 3`

we have analogical pyramid representation, and for `N > 3`

hyper-pyramid representation.

Elementwise addition: `+, -`

is supported between many `SymmetricTensors{T, N}`

while elementwise substraction: `-`

between two `SymmetricTensors{T, N}`

. Addition substraction multiplication and division `+, -, *, /`

is supported between `SymmetricTensors{T, N}`

and a number.

```
julia> x = SymmetricTensor(ones(4,4));
julia> y = SymmetricTensor(2*ones(4,4));
julia> x+y
SymmetricTensor{Float64,2}(Union{Nothing, Array{Float64,2}}[[3.0 3.0; 3.0 3.0] [3.0 3.0; 3.0 3.0]; #undef [3.0 3.0; 3.0 3.0]], 2, 2, 4, true)
julia> x*10
SymmetricTensor{Float64,2}(Union{Nothing, Array{Float64,2}}[[10.0 10.0; 10.0 10.0] [10.0 10.0; 10.0 10.0]; #undef [10.0 10.0; 10.0 10.0]], 2, 2, 4, true)
```

The function diag returns a Vector{T}, of all super-diagonal elements of a SymmetricTensor.

```
julia> data = ones(5,5,5,5);
julia> st = SymmetricTensor(data);
julia> diag(st)
5-element Array{Float64,1}:
1.0
1.0
1.0
1.0
1.0
```

To generate random Symmetric Tensor with random elements of typer `T`

form a uniform distribution on `[0,1)`

use `rand(SymmetricTensor{T, N}, n::Int, b::Int = 2)`

. Here `n`

denotes size of each mode and `b`

denotes block size. Eg. for `N = 4`

we would have `n x n x n x n`

tensor.

```
julia> using Random
julia> Random.seed!(42)
julia> x = rand(SymmetricTensor{Float64, 3}, 2)
SymmetricTensor{Float64, 3}(Union{Nothing, Array{Float64, 3}}[[0.5331830160438613 0.4540291355871424; 0.4540291355871424 0.017686826714964354]
[0.4540291355871424 0.017686826714964354; 0.017686826714964354 0.17293302893695128]], 2, 1, 2, true)
julia> Array(x)
2×2×2 Array{Float64, 3}:
[:, :, 1] =
0.533183 0.454029
0.454029 0.0176868
[:, :, 2] =
0.454029 0.0176868
0.0176868 0.172933
```

```
julia> Random.seed!(42)
julia> x = rand(SymmetricTensor{Float64, 2}, 3)
SymmetricTensor{Float64, 2}(Union{Nothing, Matrix{Float64}}[[0.5331830160438613 0.4540291355871424; 0.4540291355871424 0.017686826714964354] [0.17293302893695128; 0.9589258763297348]; nothing [0.9735659798036858]], 2, 2, 3, false)
julia> Array(x)
3×3 Matrix{Float64}:
0.533183 0.454029 0.172933
0.454029 0.0176868 0.958926
0.172933 0.958926 0.973566
```

```
julia> using Random
julia> Random.seed!(42)
julia> st = rand(SymmetricTensor{Float64, 2}, 2)
SymmetricTensor{Float64,2}(Union{Nothing, Array{Float64,2}}[[0.533183 0.454029; 0.454029 0.0176868]], 2, 1, 2, true)
julia> st[1,2]
0.4540291355871424
julia> st[2,1]
0.4540291355871424
julia> pyramidindices(st)
3-element Vector{Tuple{Int64, Int64}}:
(1, 1)
(1, 2)
(2, 2)
```

Function `pyramidindices(st::SymmetricTensor)`

returns the indices of the unique element of the give symmetric tensor

`setindex!(st::SymmetricTensor, x::Float, mulind::Int...)`

changes all symmetric tensor's elements indexed by `mulind`

to `x`

.

```
julia> st[1,2] = 10.
julia> convert(Array, st)
2×2 Array{Float64,2}:
0.533183 10.0
10.0 0.0176868
```

`julia> unfold(data::Array{T,N}, mode::Int)`

unfolds `data`

in a given mode

```
julia> a = reshape(collect(1.:8.), (2,2,2))
2×2×2 Array{Float64,3}:
[:, :, 1] =
1.0 3.0
2.0 4.0
[:, :, 2] =
5.0 7.0
6.0 8.0
julia> unfold(a, 1)
2×4 Array{Float64,2}:
1.0 3.0 5.0 7.0
2.0 4.0 6.0 8.0
julia> unfold(a, 2)
2×4 Array{Float64,2}:
1.0 2.0 5.0 6.0
3.0 4.0 7.0 8.0
julia> unfold(a, 3)
2×4 Array{Float64,2}:
1.0 2.0 3.0 4.0
5.0 6.0 7.0 8.0
```

The block usage is motivated by the paper M. D. Schatz, T. M. Low, R. A. van de Geijn, and T. G. Kolda, "Exploiting symmetry in tensors for high performance: Multiplication with symmetric tensors", SIAM Journal on Scientific Computing, 36 (2014), pp. C453–C479 https://doi.org/10.1137/130907215. There only the meaningful part of the symmetric tensor is stored in blocks to decrease the memory and computational overhead.

For application of this representation to compute cumulants, see: K. Domino, P. Gawron, Ł. Pawela "Efficient Computation of Higher-Order Cumulant Tensors",
SIAM Journal on Scientific Computing, 40 (2018) https://doi.org/10.1137/17M1149365. The selection of the optimal block size is not straight forward, however in most cases concerning cumulants one can use `2`

or `3`

.

This project was partially financed by the National Science Centre, Poland – project number 2014/15/B/ST6/05204.