CTensor tensorial algebra

Load xCoba:

<<xAct`xCoba`

The tensorial properties of CTensor

The CTensor container has a double role: it is a container to store the components of a tensor in any basis but it can also work as an usual tensor with abstract indices.

We define a manifold:

$PrePrint=ScreenDollarIndices;

DefManifold[M,4,IndexRange[a,p]]

Define an arbitrary frame field in the tangent bundle:

DefBasis[FR,TangentM,{0,1,2,3}]

Define a scalar field on the manifold:

DefTensor[s[],M]

This is a vector field on M. It automatically adds the third argument, meaning that there is no added density weight:

v=CTensor[{0,I s[],1,3s[]^2},{FR}]

The object v is now treated by xAct as if the head v had been declared with DefTensor. Hence it has all the usual properties of a tensor:

xTensorQ[v]

SlotsOfTensor[v]

SymmetryGroupOfTensor[v]

DependenciesOfTensor[v]

WeightOfTensor[v]

Dagger[v]

We can have a higher-rank tensor. For example let us make a density of weight +2 in the basis FR:

s[]Array[Signature[{##}]&,{4,4,4}]

A=CTensor[s[]Array[Signature[{##}]&,{4,4,4}],{-FR,-FR,-FR},2FR]

xTensorQ[A]

SlotsOfTensor[A]

The symmetry properties are automatically computed from the symmetry properties of the array. No additional input from the user is required.

SymmetryGroupOfTensor[A]

DependenciesOfTensor[A]

WeightOfTensor[A]

Dagger[A]===A

A CTensor with an array of zeros is replaced by the head Zero, which autoconverts to 0 when indices are supplied:

CTensor[{{0,0},{0,0}},{FR,FR}]

?Zero

Zero[a,b]

A CTensor containing a scalar is automatically converted into the scalar:

CTensor[s[],{}][]

%//InputForm

However, if it is a scalar density then the conversion is not possible because we cannot get rid from the basis dependency:

CTensor[s[],{},FR][]

%//InputForm

A self-consistency check:

CTensor[{{1,2},{3,4}},{FR}]//Catch

Finally, note that there is no collection of the CTensor objects existing in a given session. In particular they do not appear in the list $Tensors.

$Tensors

CTensor is partially compatible with the WL function SymmetrizedArray:

?SymmetrizedArray

AS=CTensor[SymmetrizedArray[s[]Array[Signature[{##}]&,{4,4,4}]],{-FR,-FR,-FR},2FR]

However, some formatting issues need to be fixed:

AS[-a,-b,-c]

Also SymmetrizedArray is not (yet) compatible with xCoba:

SymmetrizedArray[s[]Array[Signature[{##}]&,{4,4,4}]]

Indexed CTensor expressions

Following the standard behaviour in xTensor, the CTensor expressions have a special output typesetting when indices are added. This corresponds to the convention followed by MatrixForm:

	T[a]		(Column)
	T[a, b]		(Column-Row)
	T[a, b, c]	(Column-Row)-(Column)
	...		...

The convention does not distinguish between covariant or contravariant character of the abstract indices.

Vectors (both covariant and contravariant) are represented as columns. In xAct we use the index height to distinguish upper/lower, and never to distinguish rows from columns in a matrix.

v

v[a]

The color of the lines denote the basis associated to the components:

v

BasisColor[FR]

Higher-order tensors. Note the double tilde, representing weight +2 in the basis FR. In general odd indices correspond to vertical lines and even indices correspond to horizontal lines:

A

A[-a,-b,-c]

All three indices use the same basis FR, so all lines in the representation are red. This type of representation can get difficult to understand for ranks beyond 2. To help with that you can hover over a given element with the mouse, and you will get positional information:

A[-a,-b,-c]

W=CTensor[s[]Array[Signature[{##}]&,{4,4,4,4}],{-FR,-FR,-FR,-FR},2FR];

W[-a,-b,-c,-d]

To avoid formatted tensors getting too large, a given component will not be typeset if its ByteCount is larger than a given predefined limit (that you can configure):

$LargeComponentSize

$LargeComponentString

{(s[]+1)^4,(s[]+1)^5,(s[]+1)^6,(s[]+1)^7}//Expand

ByteCount/@%

Still, hovering over the “hidden component” will show you what it actually is:

CTensor[{(s[]+1)^4,(s[]+1)^5,(s[]+1)^6,(s[]+1)^7},{FR}][a]//Expand

As usual in xTensor, you can change the default formatting of the tensor and choose your own:

v[a]

We need to use Evaluate because PrintAs has attribute HoldFirst:

Attributes@PrintAs

Unprotect
PrintAs[v]="𝕍";

PrintAs[Evaluate[v]]^="𝕍";

v

v[a]

%//InputForm

That definition was actually stored as an upvalue of CTensor:

First@UpValues@CTensor

You can remove it as follows :

CTensor/:PrintAs[Evaluate[v]]=.

v[a]

Components of a CTensor may be other tensors, as long as there are no free indices (just tensor components). For example, the following is incorrect:

CTensor[{1,Basis[{-a,-FR},{b,FR}],3s[],0},{FR}][c]

%//ToBasisExpand

%//Validate

CTensor[{1,Basis[{-a,-FR},{b,FR}],3s[],0},{FR}][c]

Example of a correct usage:

CTensor[{1,TorsionPDFR[{0,FR},{1,-FR},{2,-FR}],3s[],0},{FR}][c]

%//ToBasisExpand

%//Validate

Self-consistency checks:

v

v[a,b]//Catch

v[-a]//Catch

CTensorial algebra

CTensor objects build a tensor algebra and its standard tensor algebra operations proceed with identical notations to those in xTensor.

Scalar expressions are automatically multiplied by CTensor objects:

v[a]

-7s[]^2v[a]

Add CTensor expressions with the same index configuration:

w=CTensor[{Pi,E,I,1},{FR}];

v[a]+3w[a]

Sometimes it may happen that the tensors have the same indices, but in a different order. Then indices of one CTensor are reordered before addition:

T=CTensor[{{1,2},{3,4}},{FR,FR}];

ReplaceIndex[ToCCanonical@T[b,a],{b->a,a->b}]

T[a,b]==ReplaceIndex[ToCCanonical@T[b,a],{b->a,a->b}]

T[a,b]+T[b,a]

Trace[T[a,b]+T[b,a],_CTensor]

However, the abstract indices are not changed by default, because that may interfere with the canonicalizer. The function ToCCanonical is needed then, to sort the abstract indices as determined by IndexSort:

?IndexSort

?ToCCanonical

T[b,a]

%//ToCCanonical

Note that ToCanonical is a different operation, based on the existence of symmetry of the tensor. In this case there is no symmetry and hence ToCanonical does nothing:

%%//ToCanonical

Times multiplication of two CTensor objects triggers the immediate tensor product. Examples:

v

v[a]T[b,c]

W[-a,-b,-c,-d]T[e,f]

The new CTensor object is an independent tensor and it has all the properties of a tensor, in particular it is possible to query its the symmetries:

newtensor=Head@%;

newtensor

SymmetryGroupOfTensor@newtensor

Contractions are denoted by repeated abstract indices, as usual:

A[-a,-b,-c]v[b]

% v[c]

%//Simplify

Summary

The CTensor container has a double role: it works as a container for the components of a tensor and it it is also tensor with abstract indices.

CTensor is registered as a tensor (xTensorQ returns true upon acting on it). Therefore it carries all the properties of a symbol belonging to the type Tensor. These properties can be queried with the corresponding functions.

The main difference between CTensor and any other “ordinary tensor” is that its head is not atomic. Therefore it is not introduced in the session through DefTensor and it is not contained in the list $Tensors.

There is a tensor algebra of CTensor objects similar to the tensor algebra of tensors defined with DefTensor. The components stored in the CTensor are also subject to the operations of this algebra.

Use ToCCanonical to sort abstract indices of a CTensor object according to a pre-defined convention. Symmetries of the CTensor are fully taken into account.

Introducing a metric

Once we have a metric field in our problem, there are many more things we can do. In this section we focus on how to set a metric field, and which other tensors are algebraically related to it. We can specify a metric field in several possible ways. We start with the simpler case of a metric given in a coordinate chart.

Define the chart:

$DefInfoQ=False;

DefChart[sph,M,{0,1,2,3},{t[],R[],θ[],ϕ[]}]

Construct a metric field, that is, a symmetric covariant 2-tensor:

met=CTensor[DiagonalMatrix[{-1,1,Sin[R[]]^2,Sin[R[]]^2Sin[θ[]]^2}],{-sph,-sph}]

met[-a,-b]

We have not specified that this is a metric, and there is actually no metric yet in TangentM. Therefore this object is not yet defined:

met[a,b]//Catch

Compute the inverse metric tensor with the Inv operator:

?Inv

imet=Inv[met]

Check that they are indeed inverses:

met[-a,-b]imet[b,c]//Simplify

We can also compute the metric determinant, which is a density of weight +2 in the given chart (note the third argument of CTensor in the result):

Determinant[met,sph]//Simplify

So far the metric field we have is just a normal tensor field, and xCoba does not know yet that it is going to play any special role. In order to declare it as special we need to use SetCMetric, the component counterpart of DefMetric.

Declare met as the first-metric of the tangent bundle TangentM. This will precompute a number of objects associated to the metric:

?SetCMetric

met

?SignatureOfMetric

SetCMetric[met,sph,SignatureOfMetric->{3,1,0}]

PrintxCobaCache[]

Now we can raise and lower indices, because we know what the metric is.

v=CTensor[{1,2,1,2},{sph}]

v[a]

v[-a]

met[-a,-b]v[b]

% imet[a,b]

And there is a contravariant version of the metric, identical to the inverse metric tensor we constructed before:

{met[a,b],imet[a,b]}

met[a,b]-imet[a,b]

Once we have a metric we can compute everything that is defined in terms of it. For example its Levi-Civita connection:

cd=CovDOfMetric[met]

The components of the volume form:

$Metrics

epsilon[met]

%//Normal//PowerExpand

The curvature tensors:

Riemann[cd][-a,-b,-c,-d]

Riemann[cd][-a,-b,-c,d]

Riemann[cd][a,b,-c,-d]

Riemann[cd][a,b,c,d]Riemann[cd][-a,-b,-c,-d]

Kretschmann[cd][]

Weyl[cd]

ToCTensor: changes of bases and change of index configuration

One of the most useful features of xCoba in comparison with other tensor CASs is its ability to manipulate several bases simultaneously and tensor components in different, even mixed bases. We can also have CTensor objects expressed in different bases, and we can easily change those bases, effectively implementing the basic tensorial transformation law.

Introduce a second frame on the space-time. To avoid discussing coordinate dependencies, we choose it to be a rigid transformation. Note how the transformation is provided: the new frame is associated to the first tensor slot, and in covariant form:

DefConstantSymbol@α

DefBasis[sph2,TangentM,{1,2,3,4},BasisChange->CTensor[{{-Sinh[α],-Cosh[α],0,0},{Cosh[α],Sinh[α],0,0},{0,0,1,0},{0,0,0,1}},{-sph2,sph}],BasisColor->Blue]

We show the basis change:

Basis[-{a,sph2},b]==TraceBasisDummy@SeparateBasis[sph]@Basis[-{a,sph2},b]

ToValues@ComponentArray@%

This is a vector field:

v=CTensor[{1,1,1,1},{sph}]

Change it to the new frame:

ToCTensor[v,{sph2}]

Change it back to the original frame:

ToCTensor[%,{sph}]

The identity tensor in the sph frame:

ToCTensor[delta,{-sph,sph}]

Its components do not change under a change of basis:

ToCTensor[%,{-sph2,sph2}]

The components of the metric do change:

met

ToCTensor[%,{-sph2,-sph2}]

but they still form a symmetric 2-covariant tensor field:

SymmetryGroupOfTensor[%]

We can also use ToCTensor to change the configuration of the slots of a tensor. Effectively that absorbs metric factors:

ToCTensor[met,{-sph2,sph2}]

v

ToCTensor[v,{-sph}]

Finally, we can also use ToCTensor to expand a tensor into its components:

DefTensor[S[a,-b],M]

ToCTensor[S,{sph,-sph2}]

Compare with the result from ComponentArray:

ComponentArray[S[{a,sph},{-b,-sph2}]]

Instead of specifying the change of basis with the option BasisChange at the time of definition of the new basis, we can first define the two bases and then use SetBasisChange to specify the change.

Define another basis, this time without specifying the basis change at definition time:

DefBasis[sph3,TangentM,{1,2,3,4},BasisColor->Cyan]

Now set the change of basis:

SetBasisChange[CTensor[{{Sinh[α],Cosh[α],0,0},{-Cosh[α],-Sinh[α],0,0},{0,0,1,0},{0,0,0,1}},{-sph3,sph}]]

Again, ToCTensor performs simple changes of basis with that information:

ToCTensor[v,{sph3}]

Riemann[cd]

ToCTensor[%,{-sph2,-sph2,-sph2,sph2}]//Simplify

%[-a,-b,-c,d]

ToCTensor[%%,{-sph,-sph,-sph,sph}]

%[-a,-b,-c,d]

Riemann[cd]-%%

Extracting components

Closely related to the problem of changes of bases we have that of extraction of particular components. If necessary, a change of basis will be performed automatically.

Extract a component of the metric in the same bases:

met

met[{2,-sph},{2,-sph}]

Extract a component in a different basis:

met[{2,-sph2},{2,-sph2}]

ToCTensor[met,{-sph2,-sph2}][-a,-b]

A mixture of bases:

met[{1,-sph},{2,-sph2}]

ToCTensor[met,{-sph,-sph2}][-a,-b]

We can extract a sub-CTensor expression:

met[-a,{1,-sph2}]

met[{2,-sph},-a]

Using basis indices will produce an expansion into basis objects:

met[{a,-sph},{b,-sph}]

A similar result can be obtained directly from the abstract-index expression using the following syntax:

met[-a,-b]

%//ToBasisExpand

FromBasisExpand[%,{-sph,-sph}]

Session CleanUp

We show here how to clean up a session in which we are working with CTensor containers:

Start by checking the list of bases:

$Bases

UndefBasis/@%

VisitorsOf@sph

These tensors were defined when we introduced the basis change:

UndefTensor/@%

VisitorsOf@sph2

VisitorsOf@sph3

We can proceed now with basis undefinition:

UndefBasis/@$Bases

List of tensors:

$Tensors

UndefTensor/@%

List of manifolds:

$Manifolds

UndefManifold/@%

List of constant symbols:

$ConstantSymbols

UndefConstantSymbol@α

Delete cached computations:

ClearxCobaCache[All]

PrintxCobaCache[]

Summary

The CTensor container is fully prepared to work with a metric and to perform changes of basis.

Use SetCMetric to declare a given CTensor object as the metric of our session.  The index configuration of any other CTensor can be changed with respect to this metric and the Levi-Civita covariant derivative compatible with the metric is defined.

Use SetBasisChange to declare a basis change in our session or use the option BasisChange of DefBasis at basis definition time.

Use ToCTensor as a converter to perform a change of basis on any CTensor container. The components of the change of basis matrix will be automatically replaced only if a change of basis was declared by following any of the available methods.

Representation of covariant derivatives: the CCovD container.

The key idea is to represent a covariant derivative as another connection plus a Christoffel CTensor expressing the relation between those two. Optionally, we can also specify a metric CTensor, in case the connection is associated to it. Note that in this subsection we do not need to raise and lower indices automatically, and therefore there is no need to use SetCMetric.

?CCovD

Define polar coordinates on a manifold representing the 2-plane and the standard flat metric:

<<xAct`xCoba`

<<xAct`ShowTime1`

$PrePrint=ScreenDollarIndices;

$DefInfoQ=False;

DefManifold[E2,2,{a,b,c,d,e,f,h}]

DefChart[polar,E2,{1,2},{r[],θ[]}]

met=CTensor[{{1,0},{0,r[]^2}},{-polar,-polar}]

This is the Levi-Civita connection of the metric. We see it has three arguments: the derivative of the coordinate basis, a Christoffel tensor and the metric itself.

?CovDOfMetric

cd=CovDOfMetric[met]

This is the corresponding Christoffel tensor:

Christoffel[cd,PDpolar]

%[a,-b,-c]

ToBasisExpand[%]

This derivative operator can be now used normally to perform operations:

cd[-a][r[]^3]

cd[-a][CTensor[{1,0},{polar}][b]]

cd[-a][met[-b,-c]]

cd[-a]@cd[-b]@CTensor[{0,r[]},{polar}][c]

%//ToCCanonical

CCovD derivatives are typeset with the ∇ character, and have a tooltip (when using DefTensor be careful not to use a symbol previously removed):

DefTensor[V[a],E2]

cd[-a][V[b]]

Expansion is not automatic, as usual in xTensor, and must be asked for explicitly:

ChangeCovD[%,cd,PDpolar]

ChangeCovD[%,PDpolar,cd]

%//ToCanonical

ScreenDollarIndices@%

[This is an interesting problem. The two CTensor expresions should cancel, but they don’t (TODO:. We need to force them together:]

Collect[%,_V,Simplify]

We can mix abstract and CTensor Christoffels:

ChangeCovD[cd[-a][V[b]],cd]

Torsion and curvature

Given a 2-covariant symmetric CTensor object we can use it as a metric field, from which we can compute curvature. This can be done step by step or with the command MetricCompute. In this subsection we use the simpler approach, and then we will give example of MetricCompute usage in the next section.

Compute the torsion of the covariant derivative defined in the previous subsection. It is a torsion-free derivative:

Torsion[cd]

Compute its curvature. It is a flat connection:

Riemann[cd]

Suppose we introduce a new metric:

MET=CTensor[{{1,0},{0,r[]^4}},{-polar,-polar}]

CD=CovDOfMetric[MET]

It is still torsionless:

Torsion[CD][a,-b,-c]

But now it has curvature:

Riemann[CD][-a,-b,-c,d]

SymmetryGroupOfTensor[Riemann[CD]]

That tensor is not Riemann-symmetric. We cannot automatically lower its last index (because we have not used SetCMetric), but we can manually do it:

Riemann[CD][-a,-b,-c,e]MET[-e,-d]

There is also this alternative equivalent object, which is now fully Riemann-symmetric:

RiemannDown[CD][-a,-b,-c,-d]

SymmetryGroupOfTensor[RiemannDown[CD]]

Ricci and Ricci scalar:

Ricci[CD][-a,-b]

RicciScalar[CD][]

As long as we do not use SetCMetric we can compute with many different metric objects simultaneously. Using SetCMetric would select one as being the first-metric and therefore xCoba might use it to raise and lower indices of some tensors.

Take a list of different CTensor metrics:

Table[CTensor[{{1,0},{0,r[]^n}},{-polar,-polar}],{n,0,10,2}]

Compute their respective Riemann tensors:

Riemann[CovDOfMetric[#]][-a,-b,-c,d]&/@%

TensorDerivative

The operator to compute covariant derivatives directly on CTensor objects is TensorDerivative. The result of this operation is another CTensor object.

?TensorDerivative

Check that CD is the connection compatible with the metric MET:

TensorDerivative[MET,CD]

Compute higher order Cartan scalars of the Riemann tensor :

TensorDerivative[RiemannDown[CD],CD]

TensorDerivative[RiemannDown[CD],CD,CD]

TensorDerivative[RiemannDown[CD],CD,CD,CD]

%[-a,-b,-c,-d,-e,-f,-h]

Summary

The CCovD container plays a role similar to the CTensor container but it is designed to work with covariant derivatives.

It requires a covariant derivate taken as a reference and a CTensor giving the components of the corresponding Christoffel tensor.

For the case of a Levi-Civita covariant derivative an additional argument specifying the metric tensor in the CTensor container can be given.

The container CCovD can be used as the argument of other xAct functions to do curvature computations and compute covariant derivatives.

The converter of CCovD is ToCCovD (see examples in the next section).

Use TensorDerivative to compute covariant derivatives of CTensor objects. It has a CCovD object as its second argument and its output is another CTensor object.

The xCoba cache system

The computations involving operations with CTensor objects are cached. This means that they are stored in a cache table and they are not recomputed again, unless explicitly asked for by the user.

We have a look at the list of cached computations:

?PrintxCobaCache

PrintxCobaCache[]

Access of individual elements in the cache: look at the list of DownValues of xCobaCacheTable:

Part[DownValues@xCobaCacheTable,-1]

Delete individual elements of the cache:

?ClearxCobaCache

ClearxCobaCache[{36}]

PrintxCobaCache[]

Delete the whole cache:

ClearxCobaCache[All]

PrintxCobaCache[]

Now any computation involving CTensor objects will have to be done and cached again:

$xCobaCacheVerbose=True;

RiemannDown[CD]

PrintxCobaCache[]

Note that not only the final result is cached but also all the intermediate computations leading to it. For example, if we compute the 3rd covariant derivative of the Riemann tensor that requires the computation of the first and second covariant derivatives. These are computed and cached as well:

TensorDerivative[RiemannDown[CD],CD,CD,CD]

PrintxCobaCache[]

If we try to compute again the third covariant derivative then the cache result is used rather than doing the computation again:

TensorDerivative[RiemannDown[CD],CD,CD,CD]

First entry of the cache table:

Part[DownValues[xCobaCacheTable],1]

The structure of each entry is xCobaCacheTable[InputValue, Key]. This returns the output computed from the InputValue as a DownValue of xCobaCacheTable. The computation of the output from the InputValue is carried out independently:

xCobaCacheTable[Riemann[CCovD[PDpolar,CTensor[{{{0,0},{0,-2 (r^3)}},{{0,(2/r)},{(2/r),0}}},{polar,-polar,-polar},0],CTensor[{{1,0},{0,(r^4)}},{-polar,-polar},0]]],Riemann]

Change a cache entry manually (beware that the replaced value is not correct, this is just an example):

xCobaCacheTable[Riemann[CCovD[PDpolar,CTensor[{{{0,0},{0,-2 (r^3)}},{{0,(2/r)},{(2/r),0}}},{polar,-polar,-polar},0],CTensor[{{1,0},{0,(r^4)}},{-polar,-polar},0]]],Riemann]=Zero;

Now the incorrect cached result is used, rather than recomputing the correct result:

Riemann@CD

Delete the corresponding entry in the cache in order to get back the correct result (which is cached again after recomputation):

ClearxCobaCache[{1}]

Riemann@CD

Explore the keys of the cache:

First/@DownValues@xCobaCacheTable

We can add our own computations to the cache. Use xCobaCache for that:

?xCobaCache

xCobaCache[1+1,output]

PrintxCobaCache[]

The expression output is not computed again, the cached result is used:

xCobaCache[1+1,output,Plus]

More general caching using a user defined key:

xCobaCache[inputvalue,outputvalue,key]

xCobaCache[inputvalue,outputvalue,key]

The cache is used automatically only for xCoba/CTensor computations :

inputvalue

Summary

xCoba has its own cache system to store automatically computations involving  CTensor objets:

Use PrintxCobaCache to see the contents of the cache table.

Use ClearxCobaCache to erase entries in the cache table.

Use xCobaCacheTable to change cache table entries manually.

Use xCobaCache to add entries to the cache table.