The goal of {ricci} is to provide a compact1 R
interface for performing tensor
calculations. This is achieved by allowing (upper and lower) index
labeling 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 and multiplication of tensors via
the standard operators (*
, +
, -
,
/
, ==
), raising and lowering
indices, taking symmetric or
antisymmetric tensor parts, as well as the
Kronecker product are available. Common tensors like
the Kronecker delta, Levi Civita epsilon, certain metric tensors, the
Christoffel symbols, the Riemann as well as Ricci tensors are provided.
The covariant derivative of tensor fields with respect
to any metric tensor can be evaluated. An effort was made to provide the
user with useful error messages.
{ricci} uses the calculus package (Guidotti 2022) behind the scenes to perform calculations and provides an alternative interface to a subset of its functionality. {calculus} supports symbolic calculations, allowing {ricci} to support it as well. Symbolic expressions are optionally simplified if the Ryacas package is installed.
You can install the latest CRAN release of ricci with:
install.packages("ricci")
Alternatively, you can install the development version of ricci from GitHub with:
# install.packages("pak")
::pak("lschneiderbauer/ricci") pak
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 (admittedly very artificial) example shows how to express the contraction of two tensors, and subsequent symmetrization and diagonal subsetting. For demonstration purposes we use an arbitrary array of rank 3.
library(ricci)
# numeric data
<- array(1:(2^3), dim = c(2, 2, 2))
a
# create labeled array (tensor)
%_% .(i, j, k) *
(a # mutliply with a labeled array (tensor) and raise index i and k
%_% .(i, l, k) |> r(i, k, g = g_mink_cart(2))) |>
a # * -i and +i as well as -k and +k dimension are implictely contracted
# the result is a tensor of rank 2
sym(j, l) |> # symmetrize over i and l
subst(l -> j) |> # rename index and trigger diagonal subsetting
as_a(j) # we unlabel the tensor with index order (j)
#> [1] 8 8
The same instructions work for a symbolic array:
# enable optional simplfying procedures
# (takes a toll on performance)
options(ricci.auto_simplify = TRUE)
# symbolic data
<- array(paste0("a", 1:(2^3)), dim = c(2, 2, 2))
a
%_% .(i, j, k) *
(a # mutliply with a labeled array (tensor) and raise index i and k
%_% .(i, l, k) |> r(i, k, g = g_mink_cart(2))) |>
a # * -i and +i as well as -k and +k dimension are implictely contracted
# the result is a tensor of rank 2
sym(j, l) |> # symmetrize over i and l
subst(l -> j) |> # rename index and trigger diagonal subsetting
as_a(j) # we unlabel the tensor with index order (j)
#> [1] "a1^2+a6^2-(a5^2+a2^2)" "a3^2+a8^2-(a7^2+a4^2)"
Another main feature is the covariant derivative of symbolic arrays. The following examples calculate the Hessian matrix as well as the Laplacian of the scalar function \(\sin(r)\) in spherical coordinates in three dimensions.
\[D_{ik} = \nabla_i \nabla_k \sin(r)\]
covd("sin(r)", .(i, k), g = g_eucl_sph(3)) |>
simplify()
#> <Labeled Array> [3x3] .(-i, -k)
#> [,1] [,2] [,3]
#> [1,] "-sin(r)" "0" "0"
#> [2,] "0" "r*cos(r)" "0"
#> [3,] "0" "0" "r*sin(ph1)^2*cos(r)"
\[\Delta \sin(r) = \nabla_i \nabla^i \sin(r)\]
covd("sin(r)", .(i, +i), g = g_eucl_sph(3)) |>
simplify()
#> <Scalar>
#> [1] "(2*cos(r)-r*sin(r))/r"
The covariant derivative can not only be taken from scalars, but general indexed tensors, as the following example, involving the curl of \(a\), shows.
\[\left(\nabla \times a\right)^i = \varepsilon^{i}_{\;jk} \nabla^j a^k\]
<- g_eucl_sph(3)
g <- c(0, 1, 0)
a
%_% .(+k) |> covd(.(+j), g = g) *
(a e(i, j, k) |> r(i, g = g)) |>
simplify()
#> <Labeled Array> [3] .(+i)
#> [1] "0" "0" "2/(r^3*sin(ph1)^2)"
For more details, see
vignette("ricci", package = "ricci")
. For more information
about how to use tensor fields and the covariant derivative, see
vignette("tensor_fields", package = "ricci")
.
By compact interface, we mean an interface that is concise and non-verbose. The author is of the opinion that the less we need to write in order to express an intent, the less likely are we to make mistakes.↩︎