The goal of {ricci} is to provide a compact1 R interface for performing tensor calculations. This is achieved by labeling (upper and lower) index slots of R’s array
and making use of Ricci calculus conventions to implicitly trigger contractions and diagonal subsetting. Explicit tensor operations, such as addition, subtraction, multiplication of tensors via the standard operators (*
, +
, -
), raising and lowering indices, or the Kronecker product are also available. Common tensors like the Kronecker delta, Levi Civita epsilon, and certain metric tensors are provided.
{ricci} uses the calculus package (Guidotti 2022) behind the scenes to perform calculations and simply provides an alternative interface to a subset of its functionality.
Installation
You can install the development version of ricci from GitHub with:
# install.packages("pak")
pak::pak("lschneiderbauer/ricci")
Examples
The central object is R’s array
. Adding index slot labels allows us to perform common tensor operations implicitly. After the desired calculations have been carried out we can remove the labels to obtain an ordinary array
.
The following example shows how to express the contraction of two tensors, where one index has to be raised, and subsequent diagonal subsetting. For demonstration purposes we use an arbitrary array of rank 3.
library(ricci)
# the data
a <- array(1:(2^2*3), dim = c(2,2,3))
# create labeled array (tensor)
(a %_% .(i, j, A) *
# create a labeled array (tensor) and raise index j
a %_% .(j, i, A) |> r(j, g = g_mink(2))) |>
# * -j and +j dimension are implictely contracted
# * the i-diagonal is selected
# the result is a tensor of rank 2
as_a(i, A) # we unlabel the tensor with index order (i, A)
#> [,1] [,2] [,3]
#> [1,] 5 17 29
#> [2,] 10 22 34
Below we outline more details on possible individual operations.
Creating a labeled array (tensor)
We can use the array a
to create a labeled array (tensor) with lower index labels i, j, and k:
By default, indices are assumed to be lower indices. We can use a “+” prefix to create an upper index label.
Performing calculations
Creating index labels on its own is not very interesting nor helpful. The act of labeling tensor index slots becomes useful when the labels are set such that they trigger implicit calculations, or they are combined with other tensors via multiplication or addition.
Diagonal subsetting
Repeated labels on the same position (upper or lower) will trigger diagonal subsetting.
Tensor multiplication w/ contractions
e <- a %_% .(i, j, k) * a %_% .(+i, +j, +k)
e
#> <Scalar>
#> [1] 650
# convert to number
as.numeric(e)
#> [1] 650
Tensor addition
Tensor addition is taking care of correct index slot matching (by index labels), so the position of the index does not matter.
g <- a %_% .(i, j, k) + a %_% .(j, i, k)
g
#> <Labeled Array> [2x2x3] .(-i, -j, -k)
g |> as_a(i, j, k)
#> , , 1
#>
#> [,1] [,2]
#> [1,] 2 5
#> [2,] 5 8
#>
#> , , 2
#>
#> [,1] [,2]
#> [1,] 10 13
#> [2,] 13 16
#>
#> , , 3
#>
#> [,1] [,2]
#> [1,] 18 21
#> [2,] 21 24