# Product

## Basics

Product represents a product of terms. Like any other expression, it inherits all properties of Tensor. Product adopts the following convention on its indices: the indices of product are sorted concatenated indices of its multipliers:

def t = 'f_ba*t^c_c*h^a'.t
println t.indice

   > ^{ac}_{abc}


Product sorts its arguments and collapses equal scalar terms into powers.

Product is one of the most sophisticated entities in Redberry: contractions of indices brings additional structure of mathematical graph which is reflected in Product by introducing several methods to access graph structure of product.

The data structure used to represent product contains three main blocks: numerical factor, subproduct of scalar factors (with indices.size() == 0), and tensorial content (with indices.size() != 0). Each block can be obtained via special additional methods introduced in product.

There are several features in addition to those defined in Tensor that are inherent only in Product:

 .select(positions) returns a product of elements at specified positions .remove(positions) returns a product with removed elements at specified positions .factor returns a numerical factor of product .indexlessSubProduct returns a subproduct of indexless terms, i.e. product of those terms which indices.size() == 0 .dataSubProduct returns a subproduct of indexed terms, i.e. product of those terms which indices.size() != 0 .content returns a data structure that allows to access graph structure of product

Consider examples:

def p = '2*a*b*f_ba*t^c_c*h^a'.t
println p.select(2, 3, 5)

   > a*t^{c}_{c}*h^{a}

println p.remove(2, 3, 5)

   > 2*b*f_{ba}

println p.factor

   > 2

println p.indexlessSubProduct

   > 2*b*a

println p.dataSubProduct

   > t^{c}_{c}*f_{ba}*h^{a}


## Advanced features: product content and graph structure

The presence of indices bring a graph structure of Product: each multiplier represents a graph vertex, while contractions between indices are edges. One can check whether two graph isomorphic, i.e. that two tensors are equal to within free and dummy indices relabelling using Mappings of indices.

There is a special property .content, which allows to access graph structure of Product; the object returned by .content called ProductContent. It holds tensorial part of product and additional low-level data structures used in Redberry to represent tensorial graphs. Besides, ProductContent provides several useful methods like .scalars and .nonScalar which return array of scalar sub-products and non-scalar part of tensor respectively. Consider examples:

def p = '2*a*f_a*t^ba*g_b*t^i_i*t^mn*f_nmk'.t
println p.size()

   > 8

def content = p.content
println content.size()

   > 6

println content[0..content.size()]

   > [f_a, t^ba, g_b, t^i_i, t^mn, f_nmk]

println content.scalars

   > [t^{i}_{i}, t^{ba}*g_{b}*f_{a}]

println content.nonScalar

   > t^{mn}*f_{nmk}


In order to access low-level structure that encodes Product graph there is .structureOfContractions property in ProductContent. This structure allows to check connected components of graph and check for each particular tensor with which tensors it is contracted. Without going into low-level details, let's illustrate this features by the examples:

def p = '3*c*f_i*g_b*t^i_i*t^pq*f_qpk*t^bie'.t
def content = p.content
//get graph representation
def graph = content.structureOfContractions
//array of connected components
println graph.components

   > [0, 1, 0, 1, 2, 0]

The array of components indicates whether two tensors belongs to the same connected component of graph: two tensors at i-th and j-th positions belongs to the same component if components[i] == components[j], and components[i] is the number of corresponding component.
//get position of tensor t^bie
def i = content.findIndexOf { it.equals('t^bie'.t) }
//contractions of tensor t^bie
def contractions = graph.getContractedWith(i)
for (def c in contractions) {
if (c.tensor == -1)//free index
println "Free indices:" + content[i].indices[c.indicesFrom]
else {
println "With tensor: " + content[c.tensor]
println "This indices:" + content[i].indices[c.indicesFrom]
println "With indices:" + content[c.tensor].indices[c.indicesTo]
}
}

   > Free indices:^{e}
> With tensor: g_{b}
> This indices:^{b}
> With indices:_{b}
> With tensor: f_{i}
> This indices:^{i}
> With indices:_{i}