xCoba: component calculus

Main features of xCoba:

Easy translation from expressions written in abstract index notation to expressions written with respect to a base.

Possibility of working with several different bases within the same session. Easy change of basis in tensor components.

Full integration of covariant derivatives. Computation of the connection components with respect to any frame.

Specialised functions to carry out the computation of the connection and the curvature tensors for a Levi-Civita connection in holonomic and non-holonomic frames.

xCoba is built around 3 main pillars:

Geometric set-up: DefBasis.

Storage adapted to  individual components: TensorValues.

Storage adapted to full sets of components: CTensor.

Component calculus with xCoba

Load xCoba:

<<xAct`xCoba`

New symbols introduced:

Names["xAct`xCoba`*"]

%//Length

We define a manifold:

$PrePrint=ScreenDollarIndices;

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

Type Basis

DefBasis introduces a frame field in a vector bundle. In addition to the basis name, we need to indicate the vector bundle and a set of numbers used to label each element of the base. DefBasis supplies the geometric set-up required for doing component computations.

?DefBasis

Define an arbitrary frame field in the tangent bundle:

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

We check the hierarchy of new objects defined when a frame is defined. These are the parallel covariant derivative, and the “eta tensors”:

ServantsOf@FR

ServantsOf@PDFR

List of bases:

$Bases

Component numbers of the base:

CNumbersOf@FR

Base indices are represented in input with curly brackets using the component numbers and the basis symbol.  In output form base indices are coloured (the default colour is red):

DefTensor[T[a,b],M,Antisymmetric[{a,b}]]

T[{0,FR},{1,FR}]

All the syntactic checks valid for abstract indices hold for base indices :

T[{0,FR},{1,-FR}]

%//Validate

Base indices can take numeric and non-numeric values. Use the symbols declared as abstract indices for the non-numeric values:

{T[{0,FR},{2,FR}],T[{a,FR},{b,FR}]}

Frame vectors and co-vectors. Basis and its properties

There is a special tensor used to represent any frame field. This tensor is Basis

?Basis

xTensorQ@Basis

List of frame vectors:

{Basis[{0,-FR},a],Basis[{1,-FR},a],Basis[{2,-FR},a],Basis[{3,-FR},a]}

List of frame co-vectors:

{Basis[-a,{0,FR}],Basis[-a,{1,FR}],Basis[-a,{2,FR}],Basis[-a,{3,FR}]}

Basis can be regarded as the component version of delta. There is an automatic translation:

delta[-a,{0,FR}]

Basis[-a,b]

Basis  follows the same conventions of delta  in relation to a metric. This means that certain index configurations of  Basis are not correct unless there is a metric defined in our session. If there is no metric then the convention is that the first index of Basis must be covariant and the second one contravariant. When this convention is not followed, error messages are issued:

Basis[{0,FR},-a]

Basis[{0,-FR},-a]

Main tensorial identities of Basis: duality:

Basis[-a,{b,FR}]Basis[{-c,-FR},a]

ContractBasis[%]

Basis[-a,{1,FR}]Basis[{3,-FR},a]

ContractBasis[%]

Main tensorial identities of Basis: completeness:

Basis[-a,{b,FR}]Basis[{-b,-FR},c]

ContractBasis[%]

The identity tensor:

Basis[-a,{b,FR}]Basis[{-c,-FR},a]//ContractBasis

ComponentArray[%]

Components of basis objects are automatically replaced by their values:

{Basis[{3,-FR},{1,FR}],Basis[{3,-FR},{3,FR}]}

Expansion of a tensor in Basis elements

Once we have a basis we can expand any abstract tensor or tensorial expression with respect to the given basis. Use the pair SeparateBasis, TraceBasisDummy for that.

?SeparateBasis

?TraceBasisDummy

It is possible to get rid of basis objects in an expansion by means of ContractBasis (This is achieved by carrying out the summation of the repeated  indices):

?ContractBasis

Use SeparateBasis to expand the abstract tensor ({{T, ({{a, b}, { ,  }})}})  in the basis FR:

T[a,b]

SeparateBasis[FR]@T[a,b]

Revert the expansion by summing the repeated basis indices:

ContractBasis[%]

Same computation as before:

SeparateBasis[FR]@T[a,b]

Now, expand out the repeated basis index sum:

TraceBasisDummy@%

ToCanonical also works with basis indices. Use it to simplify the expansion:

%//ToCanonical

Collect[%,_T]

Antisymmetric products of basis elements

When defining a basis, the multi-vector and multi-covector representing the wedge product of all the elements of the basis is automatically defined (the eta tensors). These can be thought of as the generalization of the classical permutation symbols (Subscript[ϵ, (Subscript[i, 1])(Subscript[…i, n])]) and (ϵ^((Subscript[i, 1])(Subscript[…i, n]))) which is naturally defined in classical tensor analysis. These objects are not true tensors, but tensorial densities and therefore we need a special notation for them in order to be able to use abstract indices for their representation .

multi-vector and multi-covector associated to the basis:

etatensors={etaUpFR[a,b,c,d],etaDownFR[-e,-f,-g,-h]}

The tildes above or below indicate that these are tensorial densities of resp weight +1 or -1:

WeightOf/@etatensors

The eta tensors are fully antisymmetric:

Union[SymmetryGroupOfTensor/@etatensors]

By definition:

etaUpFR[a,b,c,d]==Antisymmetrize[Basis[{1,-FR},a]Basis[{2,-FR},b]Basis[{3,-FR},c]Basis[{4,-FR},d]]

etaDownFR[-e,-f,-g,-h]==Antisymmetrize[Basis[-e,{1,FR}]Basis[-f,{2,FR}]Basis[-g,{3,FR}]Basis[-h,{4,FR}]]

The product of two eta tensors is the generalized Kronecker delta

Times@@etatensors

The parallel derivative

Theorem: let ℬ be a frame field defined on a vector bundle 𝒮(ℳ). Then there exists a covariant derivative (Subscript[𝒟, a]) with vanishing curvature which is compatible with the frame ℬ in the sense that its action on any frame element gives zero. Reciprocally, for any covariant derivative whose curvature is zero there exists a frame field ℬ which is compatible with the covariant derivative. The frame field is unique up to a transformation by the structure group of the vector bundle.

We will call parallel derivative to any covariant derivative that is compatible with a frame field.

List of covariant derivatives, including the parallel derivative of the basis we defined:

$CovDs

ServantsOf@PDFR

The curvature of the parallel derivative vanish:

{RiemannPDFR[-a,-b,-c,d],RicciPDFR[-a,-b]}

The parallel derivative of any frame or co-frame element is zero:

{Basis[{-b,-FR},c],Basis[-c,{b,FR}]}

PDFR[-a]/@%

If we take the covariant derivative using any other connection of any frame element we get the Christoffel tensor relating that connection to the parallel derivative of the basis :

DefCovD[CD[-a]]

Basis[-b,{c,FR}]

CD[-a][%]

Basis[-{c,FR},b]

CD[-a][%]

The components of this Christoffel tensor correspond to the so-called “Ricci rotation coefficients” or “frame connection components” in textbooks. In general the Ricci rotation coefficients have no symmetries:

SymmetryGroupOfTensor@ChristoffelCDPDFR

The parallel derivative compatible with the frame field defined in the tangent bundle will in general have torsion. This torsion is related to the commutation coefficients resulting from the Lie brackets of the basis elements:

Bracket[Basis[{-b,-FR},c],Basis[{-c,-FR},d]][a]

%//InputForm

Expansion of the covariant derivative of a tensorial expression in a frame. The Christoffel tensor are automatically introduced:

CD[-a]@T[b,c]

SeparateBasis[FR]@%

SeparateBasis[FR]@%

Expand@%

TraceBasisDummy@%

ToCanonical@%;

Collect[%,{_T,_ChristoffelCDPDFR,CD[_]@_},Simplify]

Type Chart

A chart is a particular case of frame field (holonomic frame) in which the frame elements correspond to partial derivatives of coordinates:

DefChart[SCHW,M,{0,1,2,3},{t[],r[],θ[],ϕ[]},ChartColor->Blue]

Objects which are automatically defined when introducing a chart:

ServantsOf@SCHW

ServantsOf@PDSCHW

In this case the parallel derivative is just the partial derivative of the coordinates of the chart :

PDSCHW[-a][r[]]

Since it is a coordinated frame, the bracket between frame elements is zero:

Bracket[Basis[{1,-SCHW},c],Basis[{2,-SCHW},d]][a]

Usually, the Ricci rotation coefficients in a holonomic frame are known as Christoffels symbols:

CD[-a][Basis[{-b,-SCHW},c]]

For a connection with no torsion the Christoffel symbols are symmetric:

SymmetryGroupOfTensor@ChristoffelCDPDSCHW

Summary

Frame fields are represented in xCoba using the type Basis.

Coordinate charts are a particular type of frame fields. They are represented with the type Chart.

Use SeparateBasis and TraceBasisDummy to expand any abstract tensor expression with respect to a frame.

Use ContractBasis to carry out summations in repeated (basis) indices to eliminate basis objects and recover back an abstract tensor expression.

Exercises

Exercise 1

Prove by carrying out an explicit expansion that the product of 2 eta tensors defined for a given frame is the generalized Kronecker delta:

Answer

We start by expanding the generalized Kronecker delta into Basis elements:

Times@@etatensors

%//ExpandGdelta

SeparateBasis[FR]@%

TraceBasisDummy@%;

Simplify@%

expr1=%;

Expansion of the eta tensor in terms of Basis:

etaUpFR[a,b,c,d]==Antisymmetrize[ 24Basis[{0,-FR},a]Basis[{1,-FR},b]Basis[{2,-FR},c]Basis[{3,-FR},d]]

etaDownFR[-e,-f,-g,-h]==Antisymmetrize[24Basis[-e,{0,FR}]Basis[-f,{1,FR}]Basis[-g,{2,FR}]Basis[-h,{3,FR}]]

%%[[-1]]%[[-1]]

We carry out explicitly the product of both expansions:

Expand@%;

Simplify@%

expr2=%;

We finally show that both expansions agree:

expr1-expr2;

Expand@%;

Simplify@%

Storage of components

In xCoba there are two main ways of storing tensorial or connection components:

Use the TensorValues framework. This framework is indicated when we need to carry out computations with individual components. For example compute a single component of the Riemann tensor for a metric which has a complicated algebraic expression.

Use the CTensor framework. This framework is indicated when we need to work with complete sets of components. The components can be given with respect to an arbitrary basis or tensor configuration and the change of basis or configuration is very simple from the user point of view. For example we have the set of components of a tensor in a given basis and for a given index configuration and we change the basis and/or the index configuration.

In addition there are facilities in xCoba to change from one framework to another.

Storing tensor components in the TensorValues framework

In this framework we have lists of suitable depth to store all the components of the tensor in a certain basis. If we have different basis then we will need different sets of lists.

Consider a tensor:

T[a,b]

Get the list of its components in a given frame. Use ComponentArray for that:

ToBasis[FR]@T[a,b]

ComponentArray@%

This is a list of lists:

%//MatrixForm

Storage of all the components of a tensor taking its symmetries into account:

ToBasis[FR]@T[a,b]

AllComponentValues@%

The list of components is organized according to a FoldedRule:

?FoldedRule

Extraction of the stored components. Note the separation between independent and dependent components:

TensorValues@T

Check for what index configurations there are stored components:

TensorValIDs@T

Components can be also stored individually:

ComponentValue[T[{2,FR},{1,FR}],10]

The value gets added to the corresponding FoldedRule:

TensorValues@T

However, the values are not automatically replaced:

{T[{1,FR},{0,FR}],T[{1,FR},{2,FR}]}

Use ToValues to insert the actual component values:

%//ToValues

Add single components in another basis:

ComponentValue[T[{1,SCHW},{0,SCHW}],10];ComponentValue[T[{1,SCHW},{2,SCHW}],20];

Now, there are stored components for different index configurations:

TensorValIDs@T

TensorValues@T

Explicit computation of a basis change component by component:

$CVVerbose=False;

ToBasis[SCHW]@T[a,b]==SeparateBasis[FR]@ToBasis[SCHW]@T[a,b]

TraceBasisDummy/@%

ComponentArray@%

Storage of the change of basis:

%/.Equal->ComponentValue;

TensorValues@T

Computation of a particular component. The rules of the FoldedRule are applied sequentially in the order prescribed by the FoldedRule :

T[{2,SCHW},{1,SCHW}]

%//ToValues

Summary

Any abstract index tensorial expression can be transformed into an expression with basis indices using  ToBasis.

Any basis index tensorial expression can be transformed into an array of tensor components using ComponentArray.

Individual tensor components in a given basis are stored using ComponentValue. They are stored in a FoldedRule.

The index configurations where tensor components have been stored are accessed with TensorValues.

Individual tensor components are accessed with ToValues.

Exercises

Exercise 1

Obtain the (non-tensorial) transformation law of a connection under a change of basis.

Answer

We need to choose a connection and 2 bases:

{$CovDs,$Bases}

We choose the connection CD and the basis FR as our starting basis.

Basis[-{b,FR},c]==SeparateBasis[SCHW]@Basis[-{b,FR},c]

CD[-a]/@%

ToBasis[FR]/@%

%/.ChristoffelCDPDSCHW[inds__]:>SeparateBasis[SCHW]@ChristoffelCDPDSCHW[inds]

IndexSolve[%,ChristoffelPDFRPDSCHW[{c,FR},-{a,FR},-{b,FR}]]

Patterns are not automatically added. We need to add them ourselves:

%/.ChristoffelPDFRPDSCHW[{c,FR},{-a,-FR},{-b,-FR}]->ChristoffelPDFRPDSCHW[{c_,FR},-{a_,FR},-{b_,FR}]

transformation1=%;

The term ({{Γ[𝒟,𝒟], ({{c,  ,  }, { , a, b}})}}) is the classical term which contains the partial derivatives of the change of basis matrix. Let us check it:

PDSCHW[-c]@Basis[-{a,FR},{b,SCHW}]==Hold[PDSCHW[-c]@Basis[-{a,FR},{b,SCHW}]]

SeparateBasis[FR]@%[[1]]==%[[2]]

Insert the result found above:

%/.transformation1

Times[#,Basis[-{j,FR},c]Basis[-{b,SCHW},{k,FR}]]&/@%

%/.Basis[a1__]Basis[a2__]Basis[a3__]Basis[a4__]:>ContractBasis[Basis[a1]Basis[a2]Basis[a3]Basis[a4]]

ScreenDollarIndices/@%

%/.Basis[{-j,-FR},c]Hold[PDSCHW[-c]@Basis[-{a,FR},{b,SCHW}]]->Hold[PDSCHW[{-j,-FR}]@Basis[-{a,FR},{b,SCHW}]]

We obtain the sought result:

Solve[%,Cases[%,_ChristoffelCDPDFR]]//Flatten

Equal@@First@%

nontensorialtransformation=%;

The CTensor container

The CTensor container is an alternative way of storing components of tensors. The main idea is that we have a container of information that is able to store the components of a tensor for any basis and any index configuration.

?CTensor

Example: storage of the components of a vector field in a given frame:

CTensor[{10,20,r[]/3,-4},{FR},0]

Example: storage of the components of a mixed tensor:

CTensor[RandomInteger[10,{4,4}],{FR, -FR}]

It is possible to translate the components of an already existing tensor into a CTensor representation. Use ToCTensor for that:

?ToCTensor

Examples:

ToCTensor[T,{FR,FR}]

ToCTensor[T,{SCHW,SCHW}]

ToCTensor[T,{SCHW,FR}]

ToCTensor[etaUpFR,{FR,FR,FR,FR}]

ToCTensor[Basis,{-FR,SCHW}]

The most important property of the CTensor container is that the array which appears in its first argument will change in accordance with the remaining arguments that indicate the basis and the index configuration. This change will happen under the action of  ToCTensor.  Example:

CTensor[{10,20,1/3,-4},{FR},0]

ToCTensor[%,{SCHW}]

Change of Basis

We can give specific values to the matrix which does the change between bases using SetBasisChange:

?SetBasisChange

We define a matrix of basis change for the change from SCHW to FR. This means that the elements of FR are written as a linear combination of the elements of SCHW and the coefficients of the linear combination will give us the matrix of basis change, called SCHWToFR:

SCHWToFR=ComponentArray@Basis[-{a,FR},{b,SCHW}]

Actual relation between basis elements:

DefConstantSymbol[Mass,PrintAs->"M"]

{Basis[{0,-FR},a]->Basis[{0,-SCHW},a]/Sqrt[1-2Mass/r[]],Basis[{1,-FR},a]->Basis[{1,-SCHW},a]Sqrt[1-2Mass/r[]],Basis[{2,-FR},a]->Basis[{2,-SCHW},a]/r[],Basis[{3,-FR},a]->Basis[{3,-SCHW},a]/Sin[θ[]]}

Compute from here the components of the change of basis matrix:

Map[ToBasis[SCHW],%,{2}]

Flatten@ComponentArray@%

changeofbasis=%;

SCHWToFR/.changeofbasis

SCHWToFR=%;

Set the change of basis matrix. This also computes the corresponding Jacobian:

SetBasisChange[CTensor[SCHWToFR,{-FR,SCHW}]]

JacobianFRSCHW[]

%//ToValues

%//InputForm

We check that the components of the change basis matrix are stored:

TensorValIDs@Basis

TensorValues@Basis

Now, the change of basis proceeds seamlessly for CTensor expressions:

CTensor[{10,20,1/3,-4},{FR},0]

ToCTensor[%,{SCHW}]

ToCTensor[%,{FR}]

CTensor[RandomInteger[100,{4,4}],{FR,FR}]

ToCTensor[%,{SCHW,SCHW}]

ToCTensor[%,{FR,SCHW}]

ToCTensor[%,{FR,FR}]

Introduction of a metric

We can now introduce a metric in our session and carry out the traditional computations with it (connection components, curvature tensors, etc). This is a computation that can be carried out in both the TensorValues and CTensor frameworks but here we illustrate only the latter. In the CTensor framework it is not necessary to use DefMetric.

Construct a matrix representing the metric tensor (the Schwarzschild metric in this case):

metrictensor=DiagonalMatrix[{-1+2Mass/r[],1/(1-2Mass/r[]),r[]^2,r[]^2 Sin[θ[]]^2}]

Conversion of the metric tensor into a CTensor representation:

metrictensor=CTensor[metrictensor,{-SCHW,-SCHW},0]

Expression of the metric tensor in a different base:

ToCTensor[metrictensor,{-FR,-FR}]

Declare this as the metric for the coordinate/frame computations. Use SetCMetric for that:

?SetCMetric

SetCMetric[metrictensor,SCHW,SignatureOfMetric->{3,1,0}]

Something that we haven not explained so far is that a CTensor may also work as a tensor with abstract indices. The bottom line is that a CTensor has the double role of tensor with abstract indices and container of tensor components. To see this, we use the abstract index representation of a CTensor:

metrictensor[-a,-b]

Components of the CTensor vary according to the abstract index configuration:

metrictensor[a,b]

metrictensor[a,-b]

This works because CTensor is regarded by xTensor as a true tensor:

metrictensor

xTensorQ@%

Usual representation with abstract indices:

metrictensor[-a,-b]//ToBasisExpand

Use MetricCompute to compute the classical tensors associated to our metric:

?MetricCompute

MetricCompute[metrictensor,SCHW,All,Verbose->True,Parallelize->True]

Christoffel symbols. The components of a connection are stored using the container CCovD :

?CCovD

cd1=CovDOfMetric[metrictensor]

Usual representation with abstract indices. Extract the CTensor representing the Christoffel tensor:

cd1[[2]][a,-b,-c]

ToBasisExpand@%

By using an appropriate symbol for the covariant derivative we can easily show the components of the different tensor computed in terms of  the CTensor container:

Kretschmann[cd1]

Ricci[cd1]

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

riemanntensor=%;

This representation enables us to see the mono-term symmetries of the Riemann tensor at a glance:

Each of the matrices defined by the small squares is antisymmetric. This corresponds to the antisymmetry in the last pair of indices.

The matrix defined by the big square with respect to the smaller squares is antisymmetric: antisymmetry in the first pair of indices.

The matrix defined by the big square and the individual numbers is symmetric: symmetry in the interchange of pairs of indices.

We repeat the computation but now in the orthonormal frame:

UnsetCMetric@metrictensor

ToCTensor[metrictensor,{-FR,-FR}]

metrictensororthonormal=%;

SetCMetric[metrictensororthonormal,SCHW,SignatureOfMetric->{3,1,0}]

MetricCompute[metrictensororthonormal,SCHW,All,Verbose->True,Parallelize->True]

cd2=CovDOfMetric[metrictensororthonormal];

Ricci rotation coefficients in the orthonormal frame:

cd2

cd2[[2]][a,-b,-c]

%//ToBasisExpand

Ricci[cd2]

Riemann tensor in the orthonormal frame:

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

Riemann[cd1]

We compare with the previous expression found. The change of basis is carried out automatically:

ToCTensor[Riemann[cd2],{-SCHW,-SCHW,-SCHW,-SCHW}]-Riemann[cd1]

%//Simplify

UnsetCMetric@metrictensororthonormal

Summary

CTensor is a container of information that is able to store the components of a tensor for any basis and any index configuration.

ToCTensor is a converter  that is able to compute new CTensor expressions from a given tensor or CTensor for different bases or index configurations.

SetBasisChange stores the components of a basis change matrix relating two already defined basis. These components are automatically replaced when using ToCTensor.

SetCMetric declares an appropriate CTensor expression as the metric for doing component computations.

MetricCompute computes the classical quantities (connection, curvature, etc) of a metric declared with SetCMetric. The metric can be given in coordinates or in a non-holonomic frame.

Exercises

Exercise 1

Introduce in the Schwarzschild metric defined previously the Klotsch-Ströbl coordinates:

r=x y+2m,
t = x y +2m(1+log(y/x)),
θ= Θ,
ϕ = Φ.

 find the metric in these new coordinates and carry out the computation of the curvature tensors.

Answer

Define the new coordinate chart:

DefChart[KS,M,{0,1,2,3},{x[],y[],Θ[],Φ[]},FormatBasis->{"Partials","Differentials"},ChartColor->RGBColor[0,1,0]]

Change of coordinates:

coordinatechange={r[]->x[]y[]+2Mass,t[]->x[]y[]+2Mass(1+Log[y[]/x[]]),θ[]->Θ[],ϕ[]->Φ[]}

Map[PDKS[-a],%,{2}]

Map[ToBasis[KS],%,{2}]

Flatten@ComponentArray@%

change=%;

ComponentArray@Basis[-{a,KS},{b,SCHW}]/.change

SCHWToKS=%;

SetBasisChange[CTensor[SCHWToKS,{-KS,SCHW}]]

metrictensorKS=(DiagonalMatrix[{-1+2Mass/r[],1/(1-2Mass/r[]),r[]^2,r[]^2 Sin[θ[]]^2}]/.coordinatechange)

CTensor[metrictensorKS,{-SCHW,-SCHW}]

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

metrictensorKS=%;

SetCMetric[metrictensorKS,KS,SignatureOfMetric->{3,1,0}]

Computation of curvature objects:

MetricCompute[metrictensorKS,KS,All,Verbose->True,Parallelize->True]

Christoffel tensor components:

cd3=CovDOfMetric[metrictensorKS]

The metric is Ricci flat:

Ricci[cd3]

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