Tensor


Basics

Tensor is a parent object type for all mathematical expressions used in Redberry. Thus, all expressions in Redberry are tensors. From the programming point of view that means, that Tensor is a parent class for all types of mathematical expressions used in Redberry (sums, products, etc.). Tensor defines common properties and functional shared in all its subtypes.

Common properties

There are several unifying properties of all mathematical expressions like size, indices, container properties etc. All these properties can be obtained from any expression using methods defined in Tensor. Let us consider each property in detail.

Indices

Presence of indices it a key feature of all tensorial expressions. Indices can be obtained for any expression using .indices property (or equivalently getIndices() method):

println 'g_ab * T^cd'.t.indices
   > ^cd_ab
println 'T_ba^dc'.t.indices
   > _ba^dc
(in case of pure scalars .indices will return Indices with zero size).

Indices is a feature which is inherent in any type of expression (sum, product, function, simple tensor etc.). Meanwhile, indices of different types of expressions can have slightly different nature. For example, indices of SimpleTensor will appear exactly in that order as they were specified by the user, while indices of e.g. Product will be always sorted in some way; indices of Sum are sorted free indices of its summands etc. Thus, Tensor defines only abstract method .indices while each particular subtype of Tensor is responsible for implementation. For more details see Tensors and Indices.

Container

Any tensor has a content (summands in case of Sum, multipliers in case of Product etc.). The following table summarises, how one can access container properties of expressions:

T.size() returns a size of tensor, i.e. number of summands in case of sum, number of arguments in case of function etc.
T[i] returns element at i-th position
T[i..j] returns a list of elements from i-th (inclusive) to j-th (exclusive)
T[i, j, k] returns a list of elements at specified positions
T.set(i, expr) returns a new tensor with expr at i-th position
(one can also find more special methods like remove in Sum and Product)

One can also construct Tensor from a list in the following way

 
def list = ['a', 'b', 'c']
println list as Sum // same as list.sum()
 
   > a + c + b
 
def t = 'a + b + c + d + e + f + g'.t
println t[1..4] as Product // same as t[1..4].multiply()
 
   > b*d*e

Note, that the ordering of expressions (summands in sum, or multipliers in product) can be different from that one in which the expression was inputted by the user. So, if one inputted e.g.

 
def t = 'a + b + c'.t
and then take t[0], the result will not necessary be a but also can be b or c. This ordering will be always same during a single run of Redberry, by can be changed from run to run. In order to make this ordering same in all runs one can put the following line in the beginning of the code:
 
CC.resetTensorNames(1)

Iterable

This simply means that one can iterate through expression content using for loop:

def expr = 'a*(b+e)*Sin[c]*d*(e+f)'.t
def sums = []
for (def t in expr) //iterating through expr
    if (t instanceof Sum) //taking only sums
        sums << t
println sums
   > [b+e, e+f]
println sums as Sum
   > b + 2*e + f

In such a way one can iterate only through direct "children" of expression. In order to iterate through all expression parts, one should use Tree traversal.

Creating tensors programmatically

In most cases one will input expression by hand, but often it becomes necessary to construct tensors programmatically from a given content. There are a variety ways to do that in Redberry.

First of all, Redberry allows to use simple algebraic operators on tensors:

def x = 'x'.t, y = 'y'.t
println x/y + sin(x*y) + y**3
   > x/y + Sin[x] + y**3
(however such syntax may be inconvenient for indexed expressions in some cases, since it does not reflect presence of indices)

Sometimes we need to construct expression from a given content. Here is a some useful methods that allow to do that:

sum(exprs) returns a sum of specified expressions
multiply(exprs) returns a product of specified expressions
simpleTensor(head, indices) returns a simple tensor with specified name and simple indices
field(head, indices, args) returns a tensor field with specified name, simple indices and arguments
fieldDerivative(field, dIndices, arg, order) returns a derivative of field with respect to specified argument, argument indices and order

Consider examples:

def list = ['a', 'b', 'c'].t
println sum(list)
   > a+b+c
println multiply(list)
   > a*b*c
println simpleTensor('f', '_abc'.si)
   > f_abc
println field('f', '_abc'.si, list)
   > f_abc[a,b,c]
println fieldDerivative('f_a[x_a, y_b]'.t, '_ij'.si, 1, 2)
   > f~(0,2)_{aij}[x_a, y_b]

One should be careful when using multiply() to multiply tensors with dummy indices. By default, multiply() will not rename conflicting dummy indices:

println multiply('A^a_a'.t, 'B^a_a'.t)
   > InconsistentIndicesException: Inconsistent index ^a.
If one need to rename such dummies, one can use multiplyAndRenameConflictingDummies():
println multiplyAndRenameConflictingDummies('A^a_a'.t, 'B^a_a'.t)
   > A^{a}_{a}*B^{b}_{b}

Advanced features: builders and factories

In some cases (especially when writing custom transformations) it becomes necessary to rebuild expression without knowing it particular type in advance. For this purpose, each expression provide a “builder” (via .builder property), which allows to construct tensor of same type.

For example, suppose we need to implement a “toy” transformation which simply removes all simple tensors from product or sum. This can be done in the following way:

def tr = { expr ->
    def builder = expr.builder
    for (def t in expr)
        if (t.class != SimpleTensor)
            builder << t
    builder.build()
} as Transformation

def product = 'a*(b+e)*Sin[c]*d*(e+f)'.t
println tr >> product
   > Sin[c]*(e+b)*(f+e)
def sum = 'a+b*e+Sin[c]+d+e*f'.t
println tr >> sum
   > Sin[c]+b*e+e*f

When the length of result is known in advance, one can use .factory. Consider a “toy” transformation which appends tensor a to expression:

def tr = { Tensor expr ->
    def data = expr as List
    data << 'a'.t
    expr.factory.create(data)
} as Transformation

def product = 'f[x]*Sin[a]'.t
println tr >> product
   > a*Sin[a]*f[x]
println tr >> (product as List).sum()
   > a+Sin[a]+f[x]

The general convention on builders and factories is that if t is any expression, then the result of rebuild is same:

assert t == (t.builder << (t as List)).build()
assert t == t.factory.create(t as List)

See also