# 2. Tensor arithmetic by DelayedTensor

Koki Tsuyuzaki1 and Itoshi Nikaido1*

1Laboratory for Bioinformatics Research, RIKEN Center for Biosystems Dynamics Research

*k.t.the-answer@hotmail.co.jp

#### 26 October 2021

#### Package

DelayedTensor 1.0.0

Authors: Koki Tsuyuzaki [aut, cre]
Last modified: 2021-10-26 15:33:21
Compiled: Tue Oct 26 21:02:19 2021

# 1 Setting

    suppressPackageStartupMessages(library("DelayedTensor"))
    suppressPackageStartupMessages(library("DelayedArray"))
    suppressPackageStartupMessages(library("HDF5Array"))
    suppressPackageStartupMessages(library("DelayedRandomArray"))

    darr1 <- RandomUnifArray(c(2,3,4))
    darr2 <- RandomUnifArray(c(2,3,4))

There are several settings in DelayedTensor.

First, the sparsity of the intermediate DelayedArray objects calculated inside DelayedTensor is set by `setSparse`.

Note that the sparse mode is experimental.

Whether it contributes to higher speed and lower memory is quite dependent on the sparsity of the DelayedArray, and the current implementation does not recognize the block size, which may cause out-of-memory errors, when the data is extremely huge.

Here, we specify `as.sparse` as `FALSE` (this is also the default value for now).

    DelayedTensor::setSparse(as.sparse=FALSE)

Next, the verbose message is suppressed by `setVerbose`. This is useful when we want to monitor the calculation process.

Here we specify `as.verbose` as `FALSE` (this is also the default value for now).

    DelayedTensor::setVerbose(as.verbose=FALSE)

The block size of block processing is specified by `setAutoBlockSize`. When the sparse mode is off, all the functions of DelayedTensor are performed as block processing, in which each block vector/matrix/tensor is expanded to memory space from on-disk file incrementally so as not to exceed the specified size.

Here, we specify the block size as `1E+8`.

    setAutoBlockSize(size=1E+8)

    ## automatic block size set to 1e+08 bytes (was 1e+08)

Finally, the temporal directory to store the intermediate HDF5 files during running DelayedTensor is specified by `setHDF5DumpDir`.

Note that in many systems the `/var` directory has the storage limitation, so if there is no enough space, user should specify the other directory.

    # tmpdir <- paste(sample(c(letters,1:9), 10), collapse="")
    # dir.create(tmpdir, recursive=TRUE))
    tmpdir <- tempdir()
    setHDF5DumpDir(tmpdir)

These specified values are also extracted by each getter function.

    DelayedTensor::getSparse()

    ## $delayedtensor.sparse
    ## [1] FALSE

    DelayedTensor::getVerbose()

    ## $delayedtensor.verbose
    ## [1] FALSE

    getAutoBlockSize()

    ## [1] 1e+08

    getHDF5DumpDir()

    ## [1] "D:/biocbuild/bbs-3.14-bioc/tmpdir/Rtmp67mHnf"

# 2 Tensor Arithmetic Operations

## 2.1 Unfold/Fold Operations

Unfold (a.k.a. matricizing) operations are used to reshape a tensor into a matrix.

Figure 1: Unfold/Fold Operasions

In `unfold`, `row_idx` and `col_idx` are specified to set which modes are used as the row/column.

    dmat1 <- DelayedTensor::unfold(darr1, row_idx=c(1,2), col_idx=3)
    dmat1

    ## <6 x 4> matrix of class HDF5Matrix and type "double":
    ##           [,1]      [,2]      [,3]      [,4]
    ## [1,] 0.9897312 0.9098603 0.6379992 0.6832654
    ## [2,] 0.9744007 0.6545092 0.6776798 0.9060174
    ## [3,] 0.7644012 0.4664204 0.5294220 0.1232595
    ## [4,] 0.7016701 0.6351140 0.7963029 0.6009196
    ## [5,] 0.8968425 0.4194531 0.7134620 0.6204896
    ## [6,] 0.1437884 0.5625835 0.1388142 0.5397090

`fold` is the inverse operation of `unfold`, which is used to reshape a matrix into a tensor.

In `fold`, `row_idx`/`col_idx` are specified to set which modes correspond the row/column of the output tensor and `modes` is specified to set the mode of the output tensor.

    dmat1_to_darr1 <- DelayedTensor::fold(dmat1,
        row_idx=c(1,2), col_idx=3, modes=dim(darr1))
    dmat1_to_darr1

    ## <2 x 3 x 4> array of class DelayedArray and type "double":
    ## ,,1
    ##           [,1]      [,2]      [,3]
    ## [1,] 0.9897312 0.7644012 0.8968425
    ## [2,] 0.9744007 0.7016701 0.1437884
    ##
    ## ,,2
    ##           [,1]      [,2]      [,3]
    ## [1,] 0.9098603 0.4664204 0.4194531
    ## [2,] 0.6545092 0.6351140 0.5625835
    ##
    ## ,,3
    ##           [,1]      [,2]      [,3]
    ## [1,] 0.6379992 0.5294220 0.7134620
    ## [2,] 0.6776798 0.7963029 0.1388142
    ##
    ## ,,4
    ##           [,1]      [,2]      [,3]
    ## [1,] 0.6832654 0.1232595 0.6204896
    ## [2,] 0.9060174 0.6009196 0.5397090

    identical(as.array(darr1), as.array(dmat1_to_darr1))

    ## [1] TRUE

There are some wrapper functions of `unfold` and `fold`.

For example, in `k_unfold`, mode `m` is used as the row, and the other modes are is used as the column.

`k_fold` is the inverse operation of `k_unfold`.

    dmat2 <- DelayedTensor::k_unfold(darr1, m=1)
    dmat2_to_darr1 <- k_fold(dmat2, m=1, modes=dim(darr1))
    identical(as.array(darr1), as.array(dmat2_to_darr1))

    ## [1] TRUE

    dmat3 <- DelayedTensor::k_unfold(darr1, m=2)
    dmat3_to_darr1 <- k_fold(dmat3, m=2, modes=dim(darr1))
    identical(as.array(darr1), as.array(dmat3_to_darr1))

    ## [1] TRUE

    dmat4 <- DelayedTensor::k_unfold(darr1, m=3)
    dmat4_to_darr1 <- k_fold(dmat4, m=3, modes=dim(darr1))
    identical(as.array(darr1), as.array(dmat4_to_darr1))

    ## [1] TRUE

In `rs_unfold`, mode `m` is used as the row, and the other modes are is used as the column.

`rs_fold` and `rs_unfold` also perform the same operations.

On the other hand, `cs_unfold` specifies the mode `m` as the column and the other modes are specified as the column.

`cs_fold` is the inverse operation of `cs_unfold`.

    dmat8 <- DelayedTensor::cs_unfold(darr1, m=1)
    dmat8_to_darr1 <- DelayedTensor::cs_fold(dmat8, m=1, modes=dim(darr1))
    identical(as.array(darr1), as.array(dmat8_to_darr1))

    ## [1] TRUE

    dmat9 <- DelayedTensor::cs_unfold(darr1, m=2)
    dmat9_to_darr1 <- DelayedTensor::cs_fold(dmat9, m=2, modes=dim(darr1))
    identical(as.array(darr1), as.array(dmat9_to_darr1))

    ## [1] TRUE

    dmat10 <- DelayedTensor::cs_unfold(darr1, m=3)
    dmat10_to_darr1 <- DelayedTensor::cs_fold(dmat10, m=3, modes=dim(darr1))
    identical(as.array(darr1), as.array(dmat10_to_darr1))

    ## [1] TRUE

In `matvec`, m=2 is specified as unfold.

`unmatvec` is the inverse operation of `matvec`.

    dmat11 <- DelayedTensor::matvec(darr1)
    dmat11_darr1 <- DelayedTensor::unmatvec(dmat11, modes=dim(darr1))
    identical(as.array(darr1), as.array(dmat11_darr1))

    ## [1] TRUE

`ttm` multiplies a tensor by a matrix.

`m` specifies in which mode the matrix will be multiplied.

    dmatZ <- RandomUnifArray(c(10,4))
    DelayedTensor::ttm(darr1, dmatZ, m=3)

    ## <2 x 3 x 10> array of class DelayedArray and type "double":
    ## ,,1
    ##           [,1]      [,2]      [,3]
    ## [1,] 1.3485000 0.6941990 0.8464580
    ## [2,] 1.1731256 1.0097970 0.7461863
    ##
    ## ,,2
    ##           [,1]      [,2]      [,3]
    ## [1,] 1.4358585 0.9373393 1.2081157
    ## [2,] 1.4050999 1.1710252 0.4886461
    ##
    ## ,,3
    ##          [,1]     [,2]     [,3]
    ## [1,] 2.032501 1.227932 1.502092
    ## [2,] 1.865837 1.564766 0.852667
    ##
    ## ...
    ##
    ## ,,8
    ##          [,1]     [,2]     [,3]
    ## [1,] 2.367055 1.301398 1.964102
    ## [2,] 2.439534 1.942598 1.060100
    ##
    ## ,,9
    ##           [,1]      [,2]      [,3]
    ## [1,] 1.1383138 0.6607105 1.0135860
    ## [2,] 1.1853464 1.1278515 0.5256962
    ##
    ## ,,10
    ##           [,1]      [,2]      [,3]
    ## [1,] 1.4280797 0.9918784 1.2942897
    ## [2,] 1.4524467 1.1481600 0.3739124

`ttl` multiplies a tensor by multiple matrices.

`ms` specifies in which mode these matrices will be multiplied.

    dmatX <- RandomUnifArray(c(10,2))
    dmatY <- RandomUnifArray(c(10,3))
    dlizt <- list(dmatX = dmatX, dmatY = dmatY)
    DelayedTensor::ttl(darr1, dlizt, ms=c(1,2))

    ## <10 x 10 x 4> array of class DelayedArray and type "double":
    ## ,,1
    ##            [,1]      [,2]      [,3] ...      [,9]     [,10]
    ##  [1,] 1.5190390 0.8821567 1.2062201   . 1.3554172 0.5764269
    ##  [2,] 1.5360359 0.8614088 1.2083638   . 1.3596110 0.5653496
    ##   ...         .         .         .   .         .         .
    ##  [9,] 1.6672440 0.8688626 1.2870627   . 1.4520517 0.5757880
    ## [10,] 1.5459818 0.7232411 1.1628887   . 1.3169027 0.4867257
    ##
    ## ...
    ##
    ## ,,4
    ##            [,1]      [,2]      [,3] ...      [,9]     [,10]
    ##  [1,] 0.7886859 0.4271079 0.7690934   . 0.7948722 0.3082002
    ##  [2,] 0.9090818 0.4906346 0.8457237   . 0.8890102 0.3473644
    ##   ...         .         .         .   .         .         .
    ##  [9,] 1.2276984 0.6594233 1.0648801   . 1.1490528 0.4541721
    ## [10,] 1.4387621 0.7696146 1.1705562   . 1.2949620 0.5172879

## 2.2 Vectorization

`vec` collapses a DelayedArray into a 1D DelayedArray (vector).

Figure 2: Vectorization

    DelayedTensor::vec(darr1)

    ## <24> array of class HDF5Array and type "double":
    ##       [1]       [2]       [3]         .      [23]      [24]
    ## 0.9897312 0.9744007 0.7644012         . 0.6204896 0.5397090

## 2.3 Norm Operations

`fnorm` calculates the Frobenius norm of a DelayedArray.

Figure 3: Norm Operations

    DelayedTensor::fnorm(darr1)

    ## [1] 3.293865

`innerProd` calculates the inner product value of two DelayedArray.

    DelayedTensor::innerProd(darr1, darr2)

    ## [1] 7.953491

## 2.4 Outer Product

Inner product multiplies two tensors and collapses to 0D tensor (norm). On the other hand, the outer product is an operation that leaves all subscripts intact.

Figure 4: Outer Product

    DelayedTensor::outerProd(darr1[,,1], darr2[,,1])

    ## <2 x 3 x 2 x 3> array of class HDF5Array and type "double":
    ## ,,1,1
    ##           [,1]      [,2]      [,3]
    ## [1,] 0.9262106 0.7153421 0.8392835
    ## [2,] 0.9118640 0.6566372 0.1345601
    ##
    ## ,,2,1
    ##            [,1]       [,2]       [,3]
    ## [1,] 0.13312107 0.10281367 0.12062734
    ## [2,] 0.13105909 0.09437621 0.01933986
    ##
    ## ,,1,2
    ##            [,1]       [,2]       [,3]
    ## [1,] 0.55907907 0.43179470 0.50660814
    ## [2,] 0.55041919 0.39635921 0.08122314
    ##
    ## ,,2,2
    ##            [,1]       [,2]       [,3]
    ## [1,] 0.65935223 0.50923888 0.59747042
    ## [2,] 0.64913916 0.46744789 0.09579085
    ##
    ## ,,1,3
    ##            [,1]       [,2]       [,3]
    ## [1,] 0.46788029 0.36135895 0.42396858
    ## [2,] 0.46063303 0.33170381 0.06797376
    ##
    ## ,,2,3
    ##             [,1]        [,2]        [,3]
    ## [1,] 0.045525272 0.035160628 0.041252615
    ## [2,] 0.044820106 0.032275150 0.006613922

## 2.5 Diagonal Operations

Using `DelayedDiagonalArray`, we can originally create a diagonal DelayedArray by specifying the dimensions (modes) and the values.

Figure 5: Diagonal Operations

    dgdarr <- DelayedTensor::DelayedDiagonalArray(c(5,6,7), 1:5)
    dgdarr

    ## <5 x 6 x 7> sparse array of class DelayedArray and type "integer":
    ## ,,1
    ##      [,1] [,2] [,3] [,4] [,5] [,6]
    ## [1,]    1    0    0    0    0    0
    ## [2,]    0    0    0    0    0    0
    ## [3,]    0    0    0    0    0    0
    ## [4,]    0    0    0    0    0    0
    ## [5,]    0    0    0    0    0    0
    ##
    ## ...
    ##
    ## ,,7
    ##      [,1] [,2] [,3] [,4] [,5] [,6]
    ## [1,]    0    0    0    0    0    0
    ## [2,]    0    0    0    0    0    0
    ## [3,]    0    0    0    0    0    0
    ## [4,]    0    0    0    0    0    0
    ## [5,]    0    0    0    0    0    0

Similar to the `diag` of the base package, the `diag` of DelayedTensor is used to extract and assign values to DelayedArray.

    DelayedTensor::diag(dgdarr)

    ## <5> array of class DelayedArray and type "integer":
    ## [1] [2] [3] [4] [5]
    ##   1   2   3   4   5

    DelayedTensor::diag(dgdarr) <- c(1111, 2222, 3333, 4444, 5555)
    DelayedTensor::diag(dgdarr)

    ## <5> array of class DelayedArray and type "double":
    ##  [1]  [2]  [3]  [4]  [5]
    ## 1111 2222 3333 4444 5555

## 2.6 Mode-wise Operations

`modeSum` calculates the summation for a given mode `m` of a DelayedArray. The mode specified as `m` is collapsed into 1D as follows.

Figure 6: Mode-wise Operations

    DelayedTensor::modeSum(darr1, m=1)

    ## <1 x 3 x 4> array of class DelayedArray and type "double":
    ## ,,1
    ##          [,1]     [,2]     [,3]
    ## [1,] 1.964132 1.466071 1.040631
    ##
    ## ,,2
    ##           [,1]      [,2]      [,3]
    ## [1,] 1.5643695 1.1015344 0.9820366
    ##
    ## ,,3
    ##           [,1]      [,2]      [,3]
    ## [1,] 1.3156789 1.3257249 0.8522761
    ##
    ## ,,4
    ##           [,1]      [,2]      [,3]
    ## [1,] 1.5892828 0.7241791 1.1601986

    DelayedTensor::modeSum(darr1, m=2)

    ## <2 x 1 x 4> array of class DelayedArray and type "double":
    ## ,,1
    ##          [,1]
    ## [1,] 2.650975
    ## [2,] 1.819859
    ##
    ## ,,2
    ##          [,1]
    ## [1,] 1.795734
    ## [2,] 1.852207
    ##
    ## ,,3
    ##          [,1]
    ## [1,] 1.880883
    ## [2,] 1.612797
    ##
    ## ,,4
    ##          [,1]
    ## [1,] 1.427015
    ## [2,] 2.046646

    DelayedTensor::modeSum(darr1, m=3)

    ## <2 x 3 x 1> array of class DelayedArray and type "double":
    ## ,,1
    ##          [,1]     [,2]     [,3]
    ## [1,] 3.220856 1.883503 2.650247
    ## [2,] 3.212607 2.734007 1.384895

Similar to `modeSum`, `modeMean` calculates the average value for a given mode `m` of a DelayedArray.

    DelayedTensor::modeMean(darr1, m=1)

    ## <1 x 3 x 4> array of class DelayedArray and type "double":
    ## ,,1
    ##           [,1]      [,2]      [,3]
    ## [1,] 0.9820660 0.7330357 0.5203155
    ##
    ## ,,2
    ##           [,1]      [,2]      [,3]
    ## [1,] 0.7821847 0.5507672 0.4910183
    ##
    ## ,,3
    ##           [,1]      [,2]      [,3]
    ## [1,] 0.6578395 0.6628625 0.4261381
    ##
    ## ,,4
    ##           [,1]      [,2]      [,3]
    ## [1,] 0.7946414 0.3620896 0.5800993

    DelayedTensor::modeMean(darr1, m=2)

    ## <2 x 1 x 4> array of class DelayedArray and type "double":
    ## ,,1
    ##           [,1]
    ## [1,] 0.8836583
    ## [2,] 0.6066198
    ##
    ## ,,2
    ##           [,1]
    ## [1,] 0.5985780
    ## [2,] 0.6174022
    ##
    ## ,,3
    ##          [,1]
    ## [1,] 0.626961
    ## [2,] 0.537599
    ##
    ## ,,4
    ##           [,1]
    ## [1,] 0.4756715
    ## [2,] 0.6822154

    DelayedTensor::modeMean(darr1, m=3)

    ## <2 x 3 x 1> array of class DelayedArray and type "double":
    ## ,,1
    ##           [,1]      [,2]      [,3]
    ## [1,] 0.8052140 0.4708758 0.6625618
    ## [2,] 0.8031518 0.6835017 0.3462238

## 2.7 Tensor Product Operations

There are some tensor specific product such as Hadamard product, Kronecker product, and Khatri-Rao product.

### 2.7.1 Hadamard Product

Suppose a tensor \(A \in \Re ^{I \times J}\) and a tensor \(B \in \Re ^{I \times J}\).

Hadamard product is defined as the element-wise product of \(A\) and \(B\).

Figure 7: Hadamard Product

Hadamard product can be extended to higher-order tensors.

\[ A \circ B = \begin{bmatrix} a_{11}b_{11} & a_{12}b_{12} & \cdots & a_{1J}b_{1J} \\ a_{21}b_{21} & a_{22}b_{22} & \cdots & a_{2J}b_{2J} \\ \vdots & \vdots & \ddots & \vdots \\ a_{I1}b_{I1} & a_{I2}b_{I2} & \cdots & a_{IJ}b_{IJ} \\ \end{bmatrix} \]

`hadamard` calculates Hadamard product of two DelayedArray objects.

    prod_h <- DelayedTensor::hadamard(darr1, darr2)
    dim(prod_h)

    ## [1] 2 3 4

`hadamard_list` calculates Hadamard product of multiple DelayedArray objects.

    prod_hl <- DelayedTensor::hadamard_list(list(darr1, darr2))
    dim(prod_hl)

    ## [1] 2 3 4

### 2.7.2 Kronecker Product

Suppose a tensor \(A \in \Re ^{I \times J}\) and a tensor \(B \in \Re ^{K \times L}\).

Kronecker product is defined as all the possible combination of element-wise product and the dimensions of output tensor are \({IK \times JL}\).

Figure 8: Kronecker Product

Kronecker product can be extended to higher-order tensors.

\[ A \otimes B = \begin{bmatrix} a_{11}B & a_{12}B & \cdots & a_{1J}B \\ a_{21}B & a_{22}B & \cdots & a_{2J}B \\ \vdots & \vdots & \ddots & \vdots \\ a_{I1}B & a_{I2}B & \cdots & a_{IJ}B \\ \end{bmatrix} \]

`kronecker` calculates Kronecker product of two DelayedArray objects.

    prod_kron <- DelayedTensor::kronecker(darr1, darr2)
    dim(prod_kron)

    ## [1]  4  9 16

`kronecker_list` calculates Kronecker product of multiple DelayedArray objects.

    prod_kronl <- DelayedTensor::kronecker_list(list(darr1, darr2))
    dim(prod_kronl)

    ## [1]  4  9 16

### 2.7.3 Khatri-Rao Product

Suppose a tensor \(A \in \Re ^{I \times J}\) and a tensor \(B \in \Re ^{K \times J}\).

Khatri-Rao product is defined as the column-wise Kronecker product and the dimensions of output tensor is \({IK \times J}\).

\[ A \odot B = \begin{bmatrix} a_{1} \otimes a_{1} & a_{2} \otimes a_{2} & \cdots & a_{J} \otimes a_{J} \\ \end{bmatrix} \]

Figure 9: Khatri-Rao Product

Khatri-Rao product can only be used for 2D tensors (matrices).

`khatri_rao` calculates Khatri-Rao product of two DelayedArray objects.

    prod_kr <- DelayedTensor::khatri_rao(darr1[,,1], darr2[,,1])
    dim(prod_kr)

    ## [1] 4 3

`khatri_rao_list` calculates Khatri-Rao product of multiple DelayedArray objects.

    prod_krl <- DelayedTensor::khatri_rao_list(list(darr1[,,1], darr2[,,1]))
    dim(prod_krl)

    ## [1] 4 3

## 2.8 Utilities Functions

`list_rep` replicates an arbitrary number of any R object.

    str(DelayedTensor::list_rep(darr1, 3))

    ## List of 3
    ##  $ :Formal class 'RandomUnifArray' [package "DelayedRandomArray"] with 1 slot
    ##   .. ..@ seed:Formal class 'RandomUnifArraySeed' [package "DelayedRandomArray"] with 6 slots
    ##   .. .. .. ..@ min     : num 0
    ##   .. .. .. ..@ max     : num 1
    ##   .. .. .. ..@ dim     : int [1:3] 2 3 4
    ##   .. .. .. ..@ chunkdim: int [1:3] 2 3 4
    ##   .. .. .. ..@ seeds   :List of 1
    ##   .. .. .. .. ..$ : int [1:2] 206328786 -1849245506
    ##   .. .. .. ..@ sparse  : logi FALSE
    ##  $ :Formal class 'RandomUnifArray' [package "DelayedRandomArray"] with 1 slot
    ##   .. ..@ seed:Formal class 'RandomUnifArraySeed' [package "DelayedRandomArray"] with 6 slots
    ##   .. .. .. ..@ min     : num 0
    ##   .. .. .. ..@ max     : num 1
    ##   .. .. .. ..@ dim     : int [1:3] 2 3 4
    ##   .. .. .. ..@ chunkdim: int [1:3] 2 3 4
    ##   .. .. .. ..@ seeds   :List of 1
    ##   .. .. .. .. ..$ : int [1:2] 206328786 -1849245506
    ##   .. .. .. ..@ sparse  : logi FALSE
    ##  $ :Formal class 'RandomUnifArray' [package "DelayedRandomArray"] with 1 slot
    ##   .. ..@ seed:Formal class 'RandomUnifArraySeed' [package "DelayedRandomArray"] with 6 slots
    ##   .. .. .. ..@ min     : num 0
    ##   .. .. .. ..@ max     : num 1
    ##   .. .. .. ..@ dim     : int [1:3] 2 3 4
    ##   .. .. .. ..@ chunkdim: int [1:3] 2 3 4
    ##   .. .. .. ..@ seeds   :List of 1
    ##   .. .. .. .. ..$ : int [1:2] 206328786 -1849245506
    ##   .. .. .. ..@ sparse  : logi FALSE

### 2.8.1 Bind Operations

`modebind_list` collapses multiple DelayedArray objects into single DelayedArray object.

`m` specifies the collapsed dimension.

Figure 10: Bind Operations

    dim(DelayedTensor::modebind_list(list(darr1, darr2), m=1))

    ## [1] 4 3 4

    dim(DelayedTensor::modebind_list(list(darr1, darr2), m=2))

    ## [1] 2 6 4

    dim(DelayedTensor::modebind_list(list(darr1, darr2), m=3))

    ## [1] 2 3 8

`rbind_list` is the row-wise `modebind_list` and collapses multiple 2D DelayedArray objects into single DelayedArray object.

    dim(DelayedTensor::rbind_list(list(darr1[,,1], darr2[,,1])))

    ## [1] 4 3

`cbind_list` is the column-wise `modebind_list` and collapses multiple 2D DelayedArray objects into single DelayedArray object.

    dim(DelayedTensor::cbind_list(list(darr1[,,1], darr2[,,1])))

    ## [1] 2 6

# Session information

    ## R version 4.1.1 (2021-08-10)
    ## Platform: x86_64-w64-mingw32/x64 (64-bit)
    ## Running under: Windows Server x64 (build 17763)
    ##
    ## Matrix products: default
    ##
    ## locale:
    ## [1] LC_COLLATE=C
    ## [2] LC_CTYPE=English_United States.1252
    ## [3] LC_MONETARY=English_United States.1252
    ## [4] LC_NUMERIC=C
    ## [5] LC_TIME=English_United States.1252
    ##
    ## attached base packages:
    ## [1] stats4    stats     graphics  grDevices utils     datasets  methods
    ## [8] base
    ##
    ## other attached packages:
    ##  [1] DelayedRandomArray_1.2.0 HDF5Array_1.22.0         rhdf5_2.38.0
    ##  [4] DelayedArray_0.20.0      IRanges_2.28.0           S4Vectors_0.32.0
    ##  [7] MatrixGenerics_1.6.0     matrixStats_0.61.0       BiocGenerics_0.40.0
    ## [10] Matrix_1.3-4             DelayedTensor_1.0.0      BiocStyle_2.22.0
    ##
    ## loaded via a namespace (and not attached):
    ##  [1] Rcpp_1.0.7          rTensor_1.4.8       bslib_0.3.1
    ##  [4] compiler_4.1.1      BiocManager_1.30.16 jquerylib_0.1.4
    ##  [7] rhdf5filters_1.6.0  tools_4.1.1         digest_0.6.28
    ## [10] jsonlite_1.7.2      evaluate_0.14       lattice_0.20-45
    ## [13] rlang_0.4.12        parallel_4.1.1      yaml_2.2.1
    ## [16] xfun_0.27           fastmap_1.1.0       stringr_1.4.0
    ## [19] knitr_1.36          sass_0.4.0          grid_4.1.1
    ## [22] R6_2.5.1            BiocParallel_1.28.0 rmarkdown_2.11
    ## [25] bookdown_0.24       irlba_2.3.3         Rhdf5lib_1.16.0
    ## [28] magrittr_2.0.1      BiocSingular_1.10.0 htmltools_0.5.2
    ## [31] rsvd_1.0.5          beachmat_2.10.0     dqrng_0.3.0
    ## [34] ScaledMatrix_1.2.0  stringi_1.7.5       einsum_0.1.0