knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
{ricci} uses nonstandard evaluation to specify tensor indices (see .()
). The main purpose of using nonstandard evaluation is to safe a lot of quotation marks which otherwise would clutter the code (i
instead of "i"
). Using nonstandard evaluation also allows to specify some operations in more direct way (e.g. substitution is specified as subst(x, i -> f)
is short for substitute index i
with f
).
A key point about specifying the index labels is that we can make use of Ricci calculus conventions and pack a bunch of different operations into one operator (*
). There is no separate "inner product", "outer product", "dot product", "Kronecker product" nor "element-wise product". All products are represented by a single product *
. The arrangement of the indices determines what product is carried out, as is in the spirit of Ricci calculus.
In this package we are using the terms "labeled array" and "tensor" synonymously. While in literature the word "tensor" is sometimes used to specifically refer to a "tensor field" we do not make that identification, and a tensor can exist without an underlying differential manifold.
To model tensor fields one can make use of "symbolic" array
s, i.e. arrays that do not contain numerical values, but strings that contain mathematical expressions (like "sin(x)"
for $sin(x)$, where x
can be interpreted as a manifold coordinate), see vignette("tensor_fields", package = "ricci")
for more information.
A typical workflow when working with {ricci} is
Create labeled arrays from arrays.
Perform calculations making use of Ricci calculus conventions.
Unlabel results to obtain normal array()
objects.
library(ricci) a <- array(1:(2^3), dim = c(2, 2, 2))
We can use the array a
to create a labeled array (tensor) with lower index labels i, j, and k:
$$ a_{ijk} $$
a %_% .(i, j, k)
By default, indices are assumed to be lower indices. We can use a "+" prefix to create an upper index label.
$$ a_{ij}^{\;\;k} $$
a %_% .(i, j, +k)
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.
Repeated index labels with opposite position are implicitly contracted.
$$ b_k=a_{i\;k}^{\;i} $$
b <- a %_% .(i, +i, k) b
Repeated labels on the same position (upper or lower) will trigger diagonal subsetting.
$$ c_{ik}=a_{iik} $$
c <- a %_% .(i, i, k) c
The same conventions apply for arbitrary tensor multiplication.
$$ d_{ijklmn}=a_{ijk}a_{lmn} $$
d <- a %_% .(i, j, k) * a %_% .(l, m, n) d
$$ e=a_{ijk}a^{ijk} $$
e <- a %_% .(i, j, k) * a %_% .(+i, +j, +k) e
$$ f_j=a_{ijk}a^{i\;k}_{\;j} $$
f <- a %_% .(i, j, k) * a %_% .(+i, j, +k) f
$$ g_{ijk}=a_{ijk}a_{ijk} $$
g <- a %_% .(i, j, k) * a %_% .(i, j, k) g
A Kronecker product is simply a tensor product whose underlying vector space basis is relabeled. In the present context this is realized by combining multiple index labels into one. The associated dimension to the new label is then simply the product of the dimensions associated to the old index labels respectively.
(a %_% .(i, j, k) * a %_% .(l, m, n)) |> kron(.(i, l) -> r, .(j, m) -> p, .(k, n) -> q)
Tensor addition or subtraction is taking care of correct index slot matching (by index labels), so the position of the index does not matter.
$$ h_{ijk} = a_{ijk} + a_{jik} $$
h <- a %_% .(i, j, k) + a %_% .(j, i, k) h
Taking the symmetric or antisymmetric part w.r.t. certain indices is a standard tool in Ricci calculus.
a %_% .(i, j, k) |> sym(i, j) a %_% .(i, j, k) |> sym(i, j, k) a %_% .(i, j, k) |> asym(i, j) a %_% .(i, j, k) |> asym(i, j, k)
After we are done with our calculations we usually want to retrieve an unlabeled array again to use the result elsewhere and get on with life. In contrast to a labeled array (tensor) of this package an R array
has a well-defined dimension ordering and so when stripping labels of a tensor one has to specify an index order.
g |> as_a(i, j, k)
The same works with the standard generic as.array()
. However, to avoid nonstandard evaluation in its S3 method, we wrap indices using .()
.
as.array(g, .(i, j, k))
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.