xAct tutorial

Jolyon Bloomfield

July 2013

Welcome to an overview tutorial session on xAct. In this tutorial, I want to demonstrate the power of xAct to you. I’ll do so by showing you how I solved some problems in my own research.

I’ve divided this into five separate categories. They are as follows:

1) Lovelock tensors are vanishing (basic tensor manipulation)
2) Perturbation theory to arbitrary order in scalar/tensor theories (perturbations)
3) Calculation of equations of motion from an action (utilising previously developed information)
4) Casting equations in coordinates, perturbation theory in coordinates (using a coordinate basis)
5) High order perturbation theory in arbitrary gauges about FRW (xPand)

This isn’t meant to be a pedagogical introduction to the package. I’ll write some notes to explain what I’m doing, but for details, you should look at the documentation.

Please feel free to contact me at jkb84@cornell.edu if you have any comments on this notebook. xAct has a very active forum at http://groups.google.com/group/xAct/ where you can get help with any questions you might have, typically within 24 hours.

I am not an author of any part of xAct. However, it has revolutionized my research work, and I want to share it with you.

Jolyon Bloomfield
July 2013

Installation

xAct needs to be installed before using this notebook. You can download it from http://www.xact.es/. I’ve had success in installing it on windows and linux without any problems whatsoever; I understand that some mac people have had issues though. This notebook also uses the xPand package for cosmological perturbation theory.

Part 1. Tensor Manipulation

In this section, I’m going to introduce xTensor, the main package of the xAct framework, and use it to demonstrate that the Lovelock tensor vanishes in four dimensions.

Load the package.

<<xAct`xTensor`

This command specifies a manifold. I’ll call the manifold M4, and specify it has 4 dimensions. The indices associated with this manifold are a, b, c, etc.

DefManifold[M4,4,{a,b,c,d,e,f,g,h,i,j,k,l}]

This command defines a metric. The first argument is the metric signature. The second argument is the name of the metric; note that it has to have two indices. The fact that there are minus signs indicates that the indices are lowered. CD is the name of the covariant derivative associated with the metric. Finally, I want Mathematica to print “metric” as “g”. Note that Mathematica knows that this metric is associated with M4 because of the indices that we use.

DefMetric[-1,metric[-a,-b],CD,PrintAs->"g"]

xTensor uses internal indices to manage things. This means that if I write down something with contracted indices, it will look like the following:

RicciScalarCD[]//RiemannToChristoffel

We specify the Mathematica $Pre variable, which controls printing, should fix these dollar indices appropriately.

$Pre=ScreenDollarIndices;

Then, hey presto:

RicciScalarCD[]//RiemannToChristoffel

Here is an expression that we are interested in:

Hold[RiemannCD[-a,-b,c,d] epsilonmetric[-i,-c,-d,-e] epsilonmetric[i,f,g,h]]

Note that if I release it, xTensor converts the epsilon products into Kronecker deltas as follows.

Release[%]

The reason I’ve held it is that I want to antisymmetrize it, as follows. Note that we are antisymmetrizing over 5 indices in four dimensions: this thing is automatically zero.

Antisymmetrize[%%,{-a,-b,-c,-d,-e}]//Release

Ok, that’s a little big. Let’s simplify by letting xTensor contract metrics where they appear.

result=%//ContractMetric

That’s still pretty big. But note that we have some degeneracies here. These degeneracies look like the following:

RiemannCD[-a,-b,c,d]+RiemannCD[-b,-a,d,c]

Note that RiemannCD is the Riemann tensor from the metric with covariant derivative CD. We can ask xTensor to canonicalize this expression, by using whatever symmetries are available to it to simplify things. The command to do this is ToCanonical, and is easily the most complicated command in xTensor, under the hood. Thankfully, it’s also a really easy command to use. It will fix the above as

%//ToCanonical

Because of the funny internal storage of indices, it will also fix expressions like the following:

CD[-a][CD[a][RicciScalarCD[]]]+CD[-b]@CD[b]@RicciScalarCD[]
%//ToCanonical

Note that this demonstrates two ways to write covariant derivatives. The @ command is a Mathematica shorthand (see howto/UseShorthandNotations in the help).

Let’s go back and canonicalize our expression. Remember that this is exactly zero in four dimensions.

result//ToCanonical

Now I’m going to multiply this by some stuff, and do a little bit of cleaning up.

5 /2metric[-h,-l] RiemannCD[-f,-g,e,a] %//Expand//ContractMetric//ToCanonical//Simplify

I’d like to write this in a bit nicer notation by collecting all the metric components together.

IndexCollect[Expand@%,metric[-b,-l]]

This is the Lovelock tensor. What we’ve just done is the simplest way to show that it’s zero in four dimensions. This is also the expression that we get if we vary an action containing the Gauss-Bonnet term. Actually seeing that it’s zero from that perspective is really quite difficult, however. Regardless, all of these manipulations took mere seconds. Doing this expansion by hand takes a decent amount of time.

Because all of the manipulations done here are essentially manipulating integers and nothing else, all of these results are incredible reliable, so long as you don’t make a mistake in the process of typing it in!

Clear[result]

Part 2. Perturbation Theory

Here we need to install a subpackage of xTensor, called xPert.

<<xAct`xPert`

This little bit of code just colours in perturbative indices, making things a little more readable.

Unprotect[IndexForm];
IndexForm[LI[x_]]:=ColorString[ToString[x],RGBColor[0,0,1]];
Protect[IndexForm];

We need to define a metric perturbation. We’ll call it “metpert”, and have it display as “h”. ϵ will be our expansion parameter.

DefMetricPerturbation[metric,metpert,ϵ];
PrintAs[metpert]^="h";

To write perturbed quantities, we’ll do the following.

metpert[LI[2],-a,-b]

This would be a second order perturbation. 

We need to know three commands. Firstly, we want to be able to perturb stuff.

Perturbation[metric[-a,-b],1]
Perturbation[metric[a,b],3]

What’s this triangle thing? It’s saying that it doesn’t know how to perturb the inverse metric natively. The triangle is just a wrapper noting that it needs special attention. To give it that special attention, we use the ExpandPerturbation command.

ExpandPerturbation@%

Ever tried doing that by hand?

If we want to write everything out to an appropriate order, we can use the Perturbed command.

Perturbed[metric[a,b],3]
ExpandPerturbation@%

Neat, huh? Note that perturbations are written out order by order in an expansion parameter.

Perturbed[metric[-a,-b],3]

If we only want to write (Subscript[g, ab])=(Subscript[(g^0), ab])+(Subscript[h, ab]), we need to kill the higher order perturbations, which we can do by using a Mathematica rule (that we have to write).

firstorderonly=metpert[LI[n_],__]:>0/;n>1;
Perturbed[metric[-a,-b],3]/.firstorderonly

Ok, that’s the introduction. Let’s do some amazing stuff.

ExpandPerturbation@Perturbed[ChristoffelCD[a,-b,-c],1]

Let’s write this in terms of partial derivatives. First, we separate out the metric appropriately, then we change to partial derivatives. Next, we canonicalize. Canonicalization complains about partial derivatives that don’t commute, so we tell it not to even try.

%//SeparateMetric[metric]
ChangeCovD[%,CD,PD]
ToCanonical[%,UseMetricOnVBundle->None]

This is the expression you’re probably more familiar with.

Ok, let’s do some real magic now.

Collect[ExpandPerturbation@Perturbed[RiemannCD[-a,-b,-c,d],2]/.firstorderonly//ToCanonical,ϵ]

Would you like to do that by hand?

Part 3. Equations of Motion

We can use perturbations to obtain equations of motion for an action. To obtain an equation of motion (by hand), we’d vary the fields and expand to first order, then integrate by parts appropriately, and set the variation to zero. Here, we’re going to do exactly the same thing: xTensor has a built-in variational derivative.

Let me start by defining a scalar field, and perturbations to the scalar field.

DefTensor[Phi[],M4,PrintAs->"φ"]
DefTensorPerturbation[PertPhi[LI[order]],Phi[],M4,PrintAs->"δφ"]

Let’s define some scalar functions, and a constant symbol, as follows.

DefScalarFunction[V]
DefScalarFunction[F]
DefScalarFunction[G]
DefConstantSymbol[MPlanck, PrintAs ->"(Subscript[m, p])"]

Now, let’s construct our action.

L=Sqrt[-Detmetric[]](MPlanck^2/2RicciScalarCD[]-1/2*CD[-b][Phi[]]*CD[b][Phi[]]-V[Phi[]])

We’ll just keep it simple to begin with. Next, we do the variation:

Lpert=ToCanonical@ContractMetric@ExpandPerturbation@Perturbation@L

To get the equations of motion, we now do variational derivatives with respect to our fields. Here is the scalar equation of motion.

0==(VarD[PertPhi[LI[1]],CD][Lpert]/Sqrt[-Detmetric[]]/.delta[-LI[1],LI[1]]->1//ToCanonical//Simplify)

And here is the tensor equation of motion. We vary with respect to (g^ab).

RHS=2(-VarD[metpert[LI[1],a,b],CD][Lpert]/Sqrt[-Detmetric[]]/.delta[-LI[1],LI[1]]->1//SeparateMetric[metric]//RicciToEinstein)//Expand//ContractMetric//ToCanonical;
0==RHS

Neat, huh? The command RicciToEinstein just changes the Ricci tensor to the Einstein tensor.

RicciCD[-a,-b]//RicciToEinstein

Note that the Bianchi identity is automatic:

CD[a]@EinsteinCD[-a,-b]

So let’s get conservation of stress-energy:

0==IndexCollect[CD[a]@RHS//ToCanonical,CD[-b][Phi[]]]

As expected, this is degenerate with the scalar equation of motion.

Let’s do something harder now.

L=Sqrt[-Detmetric[]](MPlanck^2/2G[Phi[]]RicciScalarCD[]-1/2*CD[-b][Phi[]]*CD[b][Phi[]]-V[Phi[]]+F[Phi[]]EinsteinCD[a,b]CD[-a][Phi[]]CD[-b][Phi[]])

Calculating the equations of motion for this action by hand is a right royal pain. Let’s see how xTensor does.

Lpert=ToCanonical@ContractMetric@ExpandPerturbation@Perturbation@L

The scalar EOM:

0==(VarD[PertPhi[LI[1]],CD][Lpert]/Sqrt[-Detmetric[]]/.delta[-LI[1],LI[1]]->1//ToCanonical//Simplify)

The metric EOM:

RHS=2(-VarD[metpert[LI[1],a,b],CD][Lpert]/Sqrt[-Detmetric[]]/.delta[-LI[1],LI[1]]->1//SeparateMetric[metric]//RicciToEinstein)//Expand//ContractMetric//ToCanonical;
0==RHS

Hideous, isn’t it? We can actually simplify this somewhat, by ordering covariant derivatives, as follows. There are commands available to determine what order you want things sorted in. I think it defaults to lexicographic ordering.

SortCovDs[RHS]//ToCanonical

Just for fun, here we derive the Lovelock tensor from the Gauss-Bonnet term in the action.

L=Sqrt[-Detmetric[]] (RicciScalarCD[]^2-4RicciCD[-a,-b]RicciCD[a,b]+RiemannCD[a,b,c,d]RiemannCD[-a,-b,-c,-d])

Lpert=ToCanonical@ContractMetric@ExpandPerturbation@Perturbation@L

RHS=2(-VarD[metpert[LI[1],a,b],CD][Lpert]/Sqrt[-Detmetric[]]/.delta[-LI[1],LI[1]]->1//SeparateMetric[metric]//RicciToEinstein)//Expand//ContractMetric//ToCanonical

This is clearly hideous. We need to make this prettier, and we’ll use a variety of techniques to do so. The following rule is a rule that says “If you’re taking a derivative of a tensor, and it could be written as a divergence, then commute derivatives until it is so.” It’s a really useful rule.

PreferDivOfRule[tens_]:=(bigexpr:(CD[b_]@CD[a_][expr_]/;(!FreeQ[expr,tens[___,ChangeIndex[b],___]]&&FreeQ[expr,tens[___,ChangeIndex[a],___]]))):>CommuteCovDs[bigexpr,CD,{a,b}];

We now write things in terms of the Ricci tensor, and apply this rule, before tidying things up.

RHS//EinsteinToRicci;
working=%/.PreferDivOfRule[RicciCD]//Expand//ContractMetric//ToCanonical

Finally, expressions are known for various derivatives of the Ricci tensor and Riemann tensor. These rules are all provided in the documentation of xTensor.

RuleDivGradRiemann=MakeRule[{CD[a]@CD[-e]@RiemannCD[-a,-b,-c,-d],-CD[-e]@CD[-d]@RicciCD[-b,-c]+CD[-e]@CD[-c]@RicciCD[-b,-d]-RicciCD[-f,-e]RiemannCD[-b,f,-c,-d]+RiemannCD[-f,-g,-c,-d]RiemannCD[-b,f,-e,g]-RiemannCD[-f,-b,-g,-d]RiemannCD[-c,g,-e,f]+RiemannCD[-f,-b,-g,-c]RiemannCD[-d,g,-e,f]},UseSymmetries->True,MetricOn->All];
RuleDivGradRicci=MakeRule[{CD[b]@CD[-c]@RicciCD[-a,-b],1/2CD[-c]@CD[-a]@RicciScalarCD[]+RicciCD[-f,-a]RicciCD[-c,f]-RicciCD[-f,-g]RiemannCD[-a,g,-c,f]},UseSymmetries->True,MetricOn->All];

Use these rules, and throw in a conversion from Ricci to Einstein and back to simplify some divergences of Ricci tensors.

working/.RuleDivGradRiemann/.RuleDivGradRicci//ToCanonical
%//RicciToEinstein//ToCanonical//EinsteinToRicci//ToCanonical;
%/2//Expand;
IndexCollect[%,metric[-a,-b]]

I’ve just divided by two in the end here and brought out the metric as a coefficient. You can compare this to the tensor we calculated above.

Clear[RHS,L,Lpert,working]

Part 4. Coordinate Systems

To use coordinate systems, we need to import a new package of the xAct system, called xCoba (short for “coordinate bases”). This package attempts to be very general, but I find it rather clunky. I recommend avoiding xCoba until the very last moment, if at all possible.

<<xAct`xCoba`

We’ll describe an almost-Schwarzschild metric. The first step is to define a basis, which we will call “B”. It’s associated with the manifold M4, and has indices {0,1,2,3}, given by the appropriate coordinates.

DefChart[B,M4,{0,1,2,3},{t[],r[],θ[],ϕ[]}]

Note that to get those Greek symbols, I typed “ESC th ESC” for θ, and “ESC ph ESC” for ϕ.

Now, we need to define our metric. We’ll use the scalar function F that we defined previously.

MatrixForm[metricarray={
{-F[r[]],0,0,0},
{0,1/F[r[]],0,0},
{0,0,r[]^2,0},
{0,0,0,r[]^2Sin[θ[]]^2}
}]

We need to tell xCoba that this is our metric:

MetricInBasis[metric,-B,metricarray]

Now, let’s calculate stuff for our metric.

MetricCompute[metric,B,All,CVSimplify->Simplify]

If we want to see these values, we first need to put it in the appropriate basis, and then ask xCoba to substitute in the appropriate value. Note that you may need to use ToValues multiple times.

RicciScalarCD[]//ToBasis[B]//ToValues

EinsteinCD[-a,-b]//ToBasis[B]//ComponentArray//ToValues//MatrixForm

Want the wave equation for a scalar field that only depends on r? No problem. We just need to tell xCoba how the scalar field looks in the appropriate basis.

DefScalarFunction[ϕ0]

ComponentValue[Phi[],ϕ0[r[]]]

Now, describe the scalar wave equation. Note that we separate out the metric dependence to make lives simpler (we don’t need to do things this way, but it is easier).

CD[a]@CD[-a]@Phi[]//SeparateMetric[metric]

Change to a basis:

%//ToBasis[B]

Now, we need to trace over the basis dummy indices.

%//TraceBasisDummy

And finally, convert all these into their appropriate values.

%//ToValues

Voila, scalar wave equation.

Unfortunately, doing perturbation theory about a background in this manner is rather clunky. I’ll demonstrate it for the scalar field; the metric gets messy.

Start by calculating the perturbation to the wave equation, setting the metric perturbations to vanishing.

EOM=ExpandPerturbation@Perturbed[CD[a]@CD[-a]@Phi[],1]/.metpert[___]->0//SeparateMetric[metric]

Repeat as above.

EOM//ToBasis[B]//TraceBasisDummy//ToValues//ToValues

Note that here we need to use ToValues twice. Also note that because we’re working in a coordinate basis, we’ve got explicit indices appearing now, rather than nice covariant things. Note that because we haven’t specified how that perturbation is behaving in the coordinate system, the covariant derivatives aren’t expanded appropriately. We can do so as follows.

DefScalarFunction[δϕ]

ComponentValue[PertPhi[LI[1]],δϕ[t[],r[],θ[],ϕ[]]]

And here is the perturbed equation of motion (to linear order in field perturbations).

EOM//ToBasis[B]//TraceBasisDummy//ToValues//ToValues

You can see why this gets a little clunky... but at least it works!

Clear[EOM]

Part 5. Perturbation Theory about FRW

This demonstration uses the xPand package, which must be installed separately. This is an incredibly powerful package. I’ll show some generic stuff, and then demonstrate how I used it to do second order perturbation theory about FRW. Because of various definitions built into xPand, I’m going to clear the kernel before proceeding.

Quit[]

<<xAct`xPand`

Once again, define a manifold, and define a metric. The optional arguments provided to the metric definition here specify how covariant derivatives will be typeset.

DefManifold[M,4,{b,c,d,e}];
DefMetric[-1,g[-b,-c],CD,{";","(OverBar[∇])"},PrintAs->"(OverBar[g])"];

To do perturbation theory, we ask xPand to define a 3+1 decomposition. n is the normal vector, h is the spatial metric, cd is the covariant derivative associated with the spatial metric. The next argument is how such covariant derivatives are written. The final argument is the form of the metric that we want. There are a lot of options, including Minkowski, FLCurved, FLFlat, Bianchi (general), BianchiA (Type A Bianchi), and BianchiI (for Type I).

SetSlicing[g,n,h,cd,{"|","(OverBar[D])"},"FLCurved"];

Next, we ask xPand to define metric perturbations (dg). We also ask it to define the matter velocity and matter velocity perturbation, u and du.

DefMetricFields[g,dg,h];
DefMatterFields[u,du,h];

The default colouring is terrible. This just makes things readable.

MakeBoxes[ϵ,StandardForm]:=
StyleBox[ToString[$PerturbationParameter],FontColor->RGBColor[0.0, 0.0, 1]]

This is a function that I define to simplify the use of the xPand master function, ToxPand.

MyToxPand[expr_,gauge_,order_]:=ToxPand[expr,dg,u,du,h, gauge,order]

This is a function we define just to make things neater.

org[expr_]:=NoScalar@Collect[ContractMetric[expr],$PerturbationParameter,ToCanonical]

Here is the general perturbed metric.

VisualizeTensor[MyToxPand[g[-b,-c],"AnyGauge",1],h]

Note the use of “AnyGauge” as our gauge. The other options are

?$ListOfGauges

Let’s turn off the vector perturbations, but leave the tensors on.

$FirstOrderVectorPerturbations=False;
$FirstOrderTensorPerturbations=True;

In Synchronous gauge, we then have

VisualizeTensor[MyToxPand[g[-b,-c],"SynchronousGauge",1],h]

Ok, let’s see what this can do.

MyToxPand[RicciScalarCD[],"NewtonGauge",2]//org

Again, this has second order perturbations. We often don’t want these; this time, we use the following rule to kill these.

killsecondorder:=(bigexpr:(expr_[LI[x_],LI[y_],___]/;x>1)):>0

Let’s try a tensor, and we’ll use physical time.

$ConformalTime=False

VisualizeTensor[MyToxPand[RicciCD[-b,-c],"NewtonGauge",2]/.killsecondorder//org,h]

Now, be honest, how long would it have taken you to do that by hand?

Ok, time for the big finale. We’d like to do perturbation theory in the action about Horndeski’s general second order scalar tensor theory. These even take the computer a long time to compute, so I’m only going to go to first order here (although I have successfully extracted second order from it).

DefScalarFunction[V]
DefScalarFunction[L3]
DefScalarFunction[L4]
DefScalarFunction[L5]

Define the first term.

term1=Sqrt[-Detg[]]V[φ[],-CD[-b]@φ[]CD[b]@φ[]/2]

And expand the first term. Note that xPand uses some conformal transformations to get everything working, so when I divide off the metric determinant, I need to divide off the scaled metric determinant. Just a detail...

term1exp=MyToxPand[term1,"NewtonGauge",1]/Sqrt[-Detg[]Scalar[ah[]]^8]//Expand//org

Define the second term.

term2=Sqrt[-Detg[]]L3[φ[],-CD[-b]@φ[]CD[b]@φ[]/2](-CD[-c]@CD[c]@φ[])

And expand the second term.

term2exp=MyToxPand[term2,"NewtonGauge",1]/Sqrt[-Detg[]Scalar[ah[]]^8]//Expand//org

Third term.

term3=Sqrt[-Detg[]](L4[φ[],-CD[-b]@φ[]CD[b]@φ[]/2]RicciScalarCD[]+Derivative[0,1][L4][φ[],-CD[-b]@φ[]CD[b]@φ[]/2](Scalar[CD[-c]@CD[c]@φ[]]^2-CD[-c]@CD[-b]@φ[]CD[c]@CD[b]@φ[]))

Expansion:

term3exp=MyToxPand[term3,"NewtonGauge",1]/Sqrt[-Detg[]Scalar[ah[]]^8]//Expand//org

And final term:

term4=Sqrt[-Detg[]](L5[φ[],-CD[-b]@φ[]CD[b]@φ[]/2]EinsteinCD[-b,-c]CD[b]@CD[c][φ[]]-1/6 Derivative[0,1][L5][φ[],-CD[-b]@φ[]CD[b]@φ[]/2](Scalar[CD[-c]@CD[c]@φ[]]^3-3Scalar[CD[-c]@CD[c]@φ[]]CD[-c]@CD[-b]@φ[]CD[c]@CD[b]@φ[]+2CD[c]@CD[-b]@φ[]CD[b]@CD[-d]@φ[]CD[d]@CD[-c]@φ[]))

It took my computer 13520 seconds to expand this to second order... Let’s just do first order.

term4exp=MyToxPand[term3,"NewtonGauge",1]/Sqrt[-Detg[]Scalar[ah[]]^8]//Expand//org

If that hasn’t impressed you, then you won’t be happy until you own a personal army of elves that write your papers for you.

Final Words

In this notebook, I’ve attempted to show you some of the power of xAct. There is LOTS more that it can do that I just haven’t had time to touch on. But that just means there’s lots for you to explore. One thing definitely worth investigating is the optional package TexAct that produces LATEX code from xTensor expressions.

Good luck!