xAct:Tensor algebra made easy with Mathematica

Barry Wardell
School of Mathematics and Statistics, University College Dublin

Amplitudes, Strong-Field Gravity and Resummation, Nordita, Stockholm, April 2026

Tensor algebra

Tensor algebra is “hard” to do by hand

Long, tedious calculations.

Highly prone to error.

Calculations difficult to reproduce reliably.

Tohru Eguchi, Peter B. Gilkey, Andrew J. Hanson, Phys. Rept. 66, 213 (1980)

Tensor algebra

Tensor algebra

Tensor algebra is “hard” to do by hand

Long, tedious calculations.

Highly prone to error.

Calculations difficult to reproduce reliably.

Tensor algebra is easy on a computer

Typical manipulations are very algorithmic.

Well suited to implementation using computer algebra.

Inherently reproducible.

More than just a more efficient way to do tensor calculations.

Many calculations go from “impossible” to straightforward.

xAct

Created by José-Maria Martin-Garcia (now at Wolfram, but xAct maintained as independent)

Free, open source (GPL) add-on package for Mathematica. Download from https://xact.es.

Robust, mature codebase. Version 1.0.0 released September 2010

Actively maintained. Latest release 29 December 2025

Extensive documentation, tutorials, etc.

Strong community of users

Active mailing list: https://groups.google.com/forum/?#!forum/xact

Friendly, helpful developer community

Non-core components developed by others: Alfonso García-Parrado, Alessandro Stecchina, Barry Wardell, Cyril Pitrou, David Brizuela, David Yllanes, Guillaume Faye, Leo Stein, Renato Portugal, Teake Nutma, Thomas Bäckdahl.

xAct

Built on a solid foundation of group-theoretic algorithms.

Mostly written in Mathematica, performance critical portion also implemented in C (MathLink) for speed.

Pre-compiled MathLink binary for macOS, Linux and Windows included in release.

Extensively tested and validated

>1,000 papers listed on xAct website, likely many many more.

No unit tests, but many correctness tests included in package notebooks.

Bugs few and far between, usually fixed very quickly once discovered.

Extremely powerful

Very generic, few domain-specific assumptions.

Arbitrary dimension, with and without a metric, torsion.

Somewhat steep learning curve.

1-step installation: Extract tarball/zip file to $UserBaseDirectory/Applications.

xAct

Modular system.

Core, canonicalization (xCore, xPerm, xTensor)

Problem-specific components

Component calculations (xCoba)

Perturbation theory (xPert)

Riemann polynomial canonicalization (Invar)

Spinors (Spinors)

...

xAct Basics

xAct basics

{xAct`xTensor`Commutator,xAct`xTensor`Anticommutator};(* Avoid warnings *)

Needs["xAct`xTensor`"](*Load the package*)

xAct basics

Define a 4-dimensional manifold M4 with indices a, b, c, ..., z (except g or h).

DefManifold[M4,4,Complement[IndexRange[a,z],{g,h}]]

xAct is very verbose by default. We can turn these messages off with a global variable:

$DefInfoQ=False;

Now define a metric, g, on our manifold. We have metric signature -1, and CD is the covariant derivative associated with g. The last argument is optional and specifies the prefix and postfix forms for the covariant derivative.

DefMetric[-1,g[-a,-b],CD,{";","∇"}]

Covariant indices are denoted by a minus sign, contravariant without the minus sign

{g[-a,-b],g[a,b],g[-a,b],g[-a,-b]g[a,c],g[-a,-b]g[a,b]}

xAct basics

When we defined our metric, xAct also created definitions for the Riemann, Ricci, Weyl, Einstein tensors, Levi-Civita and Christoffel symbols.

{RiemannCD[-a,b,-c,d],RicciCD[a,-b],RicciScalarCD[],WeylCD[-a,-b,-c,-d],EinsteinCD[-a,-b],epsilong[-a,-b,-c,-d],ChristoffelCD[a,-b,-c]}

Can change how these are displayed:

PrintAs[RiemannCD]^="R";PrintAs[RicciCD]^="R";PrintAs[RicciScalarCD]^="R";PrintAs[WeylCD]^="W";PrintAs[ChristoffelCD]^="Γ";PrintAs[EinsteinCD]^="G";PrintAs[epsilong]^="ϵ";

{RiemannCD[-a,b,-c,d],RicciCD[a,-b],RicciScalarCD[],WeylCD[-a,-b,-c,-d],EinsteinCD[-a,-b],epsilong[-a,-b,-c,-d],ChristoffelCD[a,-b,-c]}

xAct basics

Tensors with symmetries can be written in many different ways

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

ToCanonical can be used to simplify them to a canonical form using all monomial (single term) symmetries

ToCanonical[%]

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

xAct basics

We can do lots of manipulations with just these tensors

RiemannCD[-a,-b,-c,-d]//RiemannToChristoffel

Here, we see that it has introduced new dummy indices. We can replace these with more human friendly versions

%//ScreenDollarIndices

Let’s make this the default for every evaluated expression

$PrePrint=ScreenDollarIndices;

RiemannCD[-a,-b,-c,-d]//RiemannToChristoffel

xAct basics

Expand Einstein tensor in terms of the metric

EinsteinCD[-a,-b]//EinsteinToRicci

RiemannCD[-a,-b,-c,-d]//RiemannToChristoffel

%//ChristoffelToGradMetric

xAct basics

Covariant derivatives

CD[-a]@CD[-b]@RiemannCD[c,-d,e,-f]

CommuteCovDs[%,CD,{-b,-a}]

Define our own tensor fields

DefTensor[F[-a,-b],M4,Antisymmetric[{-a,-b}]];DefTensor[G[-a,-b],M4,Symmetric[{-a,-b}]];

{F[-a,-b]F[b,a],G[-a,-b]G[b,a],F[-a,-b]G[b,a]}//ToCanonical

Lie derivatives

DefTensor[H[-a],M4];
LieD[H[a]][F[-b,-c]]==LieDToCovD[LieD[H[a]][F[-b,-c]],CD]

Lagrangians in xAct

Lagrangians in xAct

The Einstein equations can be obtained from a variational derivative of the Einstein-Hilbert Lagrangian with respect to the inverse metric.

ℒ=Sqrt[-Detg[]]RicciScalarCD[]//RiemannToChristoffel//ChristoffelToGradMetric//NoScalar;

EE=Simplification[(1/(Sqrt[-Detg[]]))VarD[g[a,b],PD][ℒ],UseMetricOnVBundle->None]

Check this is indeed the Einstein equation:

ToCanonical[EE-EinsteinCD[-a,-b]//EinsteinToRicci//RiemannToChristoffel//ChristoffelToGradMetric//NoScalar,UseMetricOnVBundle->None]

Perturbation theory with xAct

Perturbation theory with xAct

We can do perturbation theory in xAct with the xPert package.

Needs["xAct`xPert`"]

Perturbation theory with xAct

Define a metric perturbation h, such that the metric has an expansion (Subscript[g, ab])+ϵ (Subscript[h, ab]^1)+(1/2)(ϵ^2)(Subscript[h, ab]^2)+...

DefMetricPerturbation[g,h,ϵ]

Make it so the index denoting perturbation order is coloured blue

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

We can use Perturbed to expand any expression to a given order

Perturbed[g[-a,-b],4]

or just get the perturbation at a specific order

Perturbation[g[-a,-b],1]

Perturbation theory with xAct

Example: linearized Einstein equation (Leor’s problem 2)

The linearized Einstein equation at first order is given by

Perturbation[-2EinsteinCD[-a,-b],1]

linearizedEE=%//ExpandPerturbation//ToCanonical//ScreenDollarIndices

It is often convenient to rewrite this in terms of a trace-reversed metric perturbation, (Subscript[(OverBar[h]), ab])=(Subscript[h, ab])-(1/2)h (Subscript[g, ab]). Define this and a rule to convert (Subscript[h, ab]) to (Subscript[(OverBar[h]), ab]).

DefTensor[hbar[-a,-b],M4,Symmetric[{-a,-b}]];
PrintAs[hbar]^="(OverBar[h])";traceReverseRule=IndexRule[h[LI[1],a_,b_],hbar[a,b]-(1/2)hbar[-c,c]g[a,b]];

Perturbation theory with xAct

Example: linearized Einstein equation (Leor’s problem 2)

Apply trace reversal to the linearized Einstein equation

traceReversedEE=linearizedEE/.traceReverseRule//ContractMetric//ToCanonical//ScreenDollarIndices

Commute covariant derivatives on third and fourth terms

commuteCovDRule=IndexRule[CD[-c_]@CD[-b_]@hbar[-a_,c_],CommuteCovDs[CD[-c]@CD[-b]@hbar[-a,c],CD,{-b,-c}]]

traceReversedEE2=traceReversedEE/.commuteCovDRule//ToCanonical

Perturbation theory with xAct

Example: linearized Einstein equation (Leor’s problem 2)

If the background is vacuum, then the Ricci tensor vanishes

vacuumTraceReversedEE=traceReversedEE2/.{RicciCD[__]->0,RicciScalarCD[]->0}

This is the familiar wave equation form, with gauge vector (Subscript[Z, a])=0 corresponding to Lorenz gauge

DefTensor[Z[-a],M4]

toGauge={IndexRule[CD[-c_][hbar[c_,a_]],Z[a]],IndexRule[CD[-c_][hbar[a_,c_]],Z[a]]};

ToCanonical[vacuumTraceReversedEE/.toGauge]

Perturbation theory with xAct

Example: higher-order linearized Einstein equation

Second order perturbations just as easy:

Perturbation[EinsteinCD[-a,-b],2]//ExpandPerturbation//ToCanonical//ScreenDollarIndices//ContractMetric//ToCanonical

Perturbation theory with xAct

Example: higher-order linearized Einstein equation

And third order

Perturbation[EinsteinCD[-a,-b],3]//ExpandPerturbation//ToCanonical//ScreenDollarIndices//ContractMetric//ToCanonical

Perturbation theory with xAct

Example: higher-order linearized Einstein equation

Feeling ambitious: (8^th) order

Perturbation[EinsteinCD[-a,-b],8]//ExpandPerturbation//Expand;

This produces an enormous expression with 47,488 terms:

Length[%]

Let’s look at the last 48 terms

%%[[-48;;]]//ContractMetric//ToCanonical

Component and tetrad calculations with xAct

Component calculations with xAct

Metrics package: convenient wrapper for xCoba [https://github.com/barrywardell/Metrics].

Provides a database of common metrics (Schwarzschild, Kerr, ...).

Automates repetitive xCoba boilerplate

Define manifold

Define metric

Define coordinate tensors

Define coordinate chart

Set metric components

Correctness tests validate database entries.

Component calculations with xAct

Before we load the Metrics package, we first need to undefine the metric, tensors, etc we defined earlier, as the Metrics package will define new ones.

Undef[h]

Let’s disable some more information messages:

$UndefInfoQ=False;

And now undefine the metric and manifold

UndefMetric[g]

UndefTensor/@{F,G,H,hbar,Z};

UndefManifold[M4]

ℒ=.

Component calculations with xAct

Calculations in Kerr spacetime

Now load the Metrics package. This loads xCoba (we silence the usual info messages printed on startup).

Block[{Print},Get["Metrics`LoadMetric`"];]
$CVVerbose=False;

Load definitions for Kerr metric

LoadMetric["Kerr"];

Compute the connection, Riemann tensor, etc.

Block[{Print},MetricCompute[g,Kerr,All,CVSimplify->Simplify]];

Component calculations with xAct

Evaluate components in a coordinate basis

Evaluate the Kerr metric in the Kerr Boyer-Lindquist basis:

{ToBasis[Kerr][g[-b,-c]],InputForm[ToBasis[Kerr][g[-b,-c]]]}

It’s annoying to have to type in the indices and their basis each time, so we define some shorthands

bK={b,Kerr};cK={c,Kerr};

Now, let’s evaluate the Boyer-Lindquist components of the metric:

ComponentArray[g[-bK,-cK]]//ToValues

Component calculations with xAct

Example: Kinnersley tetrad

We learned in Leor’s first lecture about null tetrads and how they are well-adapted to studying Kerr spacetime. The Kinnersley tetrad is an example of such a tetrad. First we define the four tetrad vectors:

DefTensor[lT[b],TangentKerrM,PrintAs->"l"];
DefTensor[nT[b],TangentKerrM,PrintAs->"n"];
DefTensor[mT[b],TangentKerrM,Dagger->Complex,PrintAs->"m"];

PrintAs[mT†]^="(OverBar[m])";

Component calculations with xAct

Example: Kinnersley tetrad

Now we  set their Boyer - Lindquist coordinate components :

ϱ=-1/(r[]-I a Cos[θ[]]);ϱb=-1/(r[]+I a Cos[θ[]]);
Λ=((r[])^2)+(a^2);Δ=Λ-2M r[];Σ=((r[])^2)+(a^2)((Cos[θ[]])^2);
AllComponentValues[lT[bK],(1/Δ){Λ,Δ,0,a}];AllComponentValues[nT[bK],(1/(2Σ)){Λ,-Δ,0,a}];
AllComponentValues[mT[bK],((-ϱb)/(Sqrt[2])){I a Sin[θ[]],0,1,(I/(Sin[θ[]]))}];
AllComponentValues[mT†[bK],((-ϱ)/(Sqrt[2])){-I a Sin[θ[]],0,1,((-I)/(Sin[θ[]]))}];

Also set the covariant components by contracting with the metric:

Do[AllComponentValues[basis[-cK],Simplify[ToValues[ComponentArray[TraceBasisDummy[basis[bK]g[-bK,-cK]]]]]];,{basis,{lT,nT,mT,mT†}}];

Component calculations with xAct

Example: tetrad components of the metric (Leor’s problem 7)

Simplify[ComponentArray[ToBasis[Kerr][2Symmetrize[-lT[-b]nT[-c]+mT[-b]mT†[-c],{-b,-c}]]]//ToValues]

Simplify[ComponentArray[ToBasis[Kerr][g[-b,-c]]]//ToValues]

Simplify[%==%%]

Component calculations with xAct

Change of basis in xAct

Define a generic Newman-Penrose tetrad

DefBasis[NP,TangentKerrM,{1,2,3,4},BasisColor->RGBColor[0,0,1]]

Under conjugation, the basis transforms by interchanging 3 and 4

SetDaggerMatrix[NP,{{1,0,0,0},{0,1,0,0},{0,0,0,1},{0,0,1,0}}]

The metric in terms of the NP tetrad is (Subscript[g, ab])=-2(Subscript[l, (a])(Subscript[n, b)])+2(Subscript[m, (a])(Subscript[(OverBar[m]), b)])

MetricInBasis[g,-NP,{{0,-1,0,0},{-1,0,0,0},{0,0,0,1},{0,0,1,0}}];
MetricInBasis[g,NP,{{0,-1,0,0},{-1,0,0,0},{0,0,0,1},{0,0,1,0}}];

basisToTetrad={Basis[e_,{i_,NP}]:>{-nT[e],-lT[e],mT†[e],mT[e]}[[i]],Basis[{i_,-NP},e_]:>{lT[e],nT[e],mT[e],mT†[e]}[[i]]};

Calculations in Kerr spacetime

Example: tetrad components of the Riemann tensor

ψ0=RiemannCD[{1,-NP},{3,-NP},{1,-NP},{3,-NP}];
ψ1=RiemannCD[{1,-NP},{2,-NP},{1,-NP},{3,-NP}];
ψ2=RiemannCD[{1,-NP},{3,-NP},{4,-NP},{2,-NP}];ψ3=RiemannCD[{2,-NP},{1,-NP},{2,-NP},{4,-NP}];
ψ4=RiemannCD[{2,-NP},{4,-NP},{2,-NP},{4,-NP}];

BasisExpand[ψ0,Kerr]/.basisToTetrad//ToValues//Simplify

BasisExpand[ψ1,Kerr]/.basisToTetrad//ToValues//Simplify

BasisExpand[ψ2,Kerr]/.basisToTetrad//ToValues//Simplify

%==-(M/(((r[]-I a Cos[θ[]]))^3))//Simplify

BasisExpand[ψ3,Kerr]/.basisToTetrad//ToValues//Simplify

BasisExpand[ψ4,Kerr]/.basisToTetrad//ToValues//Simplify

Component calculations with xAct

Exercise: Geodesics of Kerr spacetime

1. (a)

Define a vector that represents a generic Killing vector

DefTensor[Kv[b],TangentKerrM]

The Killing equation as a Lie derivative of the metric:

LieD[Kv[b]][g[-c,-d]]//LieDToCovD

Equivalent equation in terms of a covariant derivative of (K^α)

2Symmetrize[CD[-c][Kv[-d]],{-c,-d}]//CovDToChristoffel//SeparateMetric[g]//ToCanonical//ChristoffelToGradMetric//Expand

%==%%//ToCanonical

1. (b)

Start with a quantity that must be zero by the Killing equation

expr=Antisymmetrize[CD[-c]@CD[-d]@Kv[-b],{-c,-d}]+Antisymmetrize[CD[-b]@CD[-c]@Kv[-d],{-b,-c}]-Antisymmetrize[CD[-d]@CD[-b]@Kv[-c],{-d,-b}]

Simplify using the Killing equation to get the left-hand side we want:

lhs=expr/.{CD[-c]@Kv[-d]->-CD[-d]@Kv[-c],CD[-b]@Kv[-d]->-CD[-d]@Kv[-b],CD[-b]@Kv[-c]->-CD[-c]@Kv[-b]}//ToCanonical

Commute covariant derivatives and apply the Bianchi identitiy to get the right-hand side we want:

rhs=SortCovDs[expr]/.IndexRule[RiemannCD[-d,-c,-b,e_],-(RiemannCD[-c,-b,-d,e]+RiemannCD[-b,-d,-c,e])]//ToCanonical

%==RiemannCD[e,-c,-d,-b]Kv[-e]//ToCanonical

g[d,c](lhs-rhs)==0//ContractMetric

1. (c)

DefTensor[U[b],TangentKerrM];

geodesicEq=IndexRule[U[b_]CD[-b_][U[c_]],0];

Expand[U[c]CD[-c][U[b]Kv[-b]]]/.geodesicEq

%/.IndexRule[CD[-b_]@Kv[-c_],Antisymmetrize[CD[-b]@Kv[-c],{-b,-c}]]//ToCanonical

1. (d)

DefTensor[ξt[b],TangentKerrM]

AllComponentValues[ξt[bK],{1,0,0,0}];

AllComponentValues[ξt[-bK],Simplify[ToValues[ComponentArray[TraceBasisDummy[ξt[cK]g[-cK,-bK]]]]]];

Simplify[ToValues[ComponentArray[TraceBasisDummy[FixedPoint[ToBasis[Kerr],Symmetrize[CD[-c][ξt[-d]],{-c,-d}]]]]]]

DefTensor[ξϕ[b],TangentKerrM]

AllComponentValues[ξϕ[bK],{0,0,0,1}];

AllComponentValues[ξϕ[-bK],Simplify[ToValues[ComponentArray[TraceBasisDummy[ξϕ[cK]g[-cK,-bK]]]]]];

Simplify[ToValues[ComponentArray[TraceBasisDummy[FixedPoint[ToBasis[Kerr],Symmetrize[CD[-c][ξϕ[-d]],{-c,-d}]]]]]]

1. (e)

utuϕ=First[FullSimplify[Solve[{ToValues[TraceBasisDummy[g[-bK,{1,-Kerr}]U[bK]]]==-ℰ,ToValues[TraceBasisDummy[g[-bK,{4,-Kerr}]U[bK]]]==ℒ},{U[{1,Kerr}],U[{4,Kerr}]}]]]

2. (a)

First we define the Killing tensor

DefTensor[K[b,c],TangentKerrM,Symmetric[{b,c}]];

AllComponentValues[K[bK,cK],Simplify[ToValues[ComponentArray[2Σ (1/2)(lT[bK]nT[cK]+lT[cK]nT[bK])+((r[])^2)g[bK,{c,Kerr}]]]]];

AllComponentValues[K[bK,-cK],FullSimplify[ToValues[ComponentArray[TraceBasisDummy[ToBasis[Kerr][K[b,e]g[-c,-e]]]]]]];

AllComponentValues[K[-bK,-cK],FullSimplify[ToValues[ComponentArray[TraceBasisDummy[ToBasis[Kerr][K[d,e]g[-b,-d]g[-c,-e]]]]]]];

Verify that it satisfies Killing’s equation

KillingEq=Symmetrize[CD[-b][K[-c,-d]],{-b,-c,-d}]

Simplify[ToValues[ComponentArray[TraceBasisDummy[FixedPoint[ToBasis[Kerr],KillingEq]]]]]

2. (b)

Expand[U[d]CD[-d][U[b]U[c]K[-b,-c]]]/.geodesicEq

%/.IndexRule[CD[-d_]@K[-b_,-c_],CD[-d]@K[-b,-c]-Symmetrize[CD[-d]@K[-b,-c],{-b,-c,-d}]]//ToCanonical

2. (c)

K[bK,-cK]ξt[cK]//TraceBasisDummy//ComponentArray//ToValues

-((a^2)ξt[bK]+a ξϕ[bK])//ComponentArray//ToValues

%==%%

2. (d)

Simplify[ToValues[ComponentArray[K[bK,cK]-(2Σ (1/2)(mT[bK]mT†[cK]+mT[cK]mT†[bK])-(a^2)((Cos[θ[]])^2)g[bK,cK])]]]

2. (e)

timelike=IndexRule[U[-b_]U[b_],-1];

uθ=U[{3,Kerr}]->ToValues[TraceBasisDummy[U[-bK]g[bK,{3,Kerr}]]];

𝒦eq=FullSimplify[ToValues[TraceBasisDummy[ToBasis[Kerr][ContractMetric[(2Σ (1/2)(mT[-b]mT†[-c]+mT[-c]mT†[-b])-(a^2)((Cos[θ[]])^2)g[-b,-c])U[b]U[c]]/.timelike]]]/.utuϕ/.uθ]

Simplify[𝒦eq==((U[{3,-Kerr}])^2)+(a^2)((Cos[θ[]])^2)+(a^2)(ℰ^2)((Sin[θ[]])^2)+((ℒ^2)/((Sin[θ[]])^2))-2a ℰ ℒ]

UθEq=U[{3,-Kerr}]/.Solve[𝒦eq==𝒦,U[{3,-Kerr}]][[2]]

2. (f)

Simplify[((U[{2,-Kerr}])^2)/.First[Solve[ToValues[TraceBasisDummy[g[bK,cK]U[-bK]U[-cK]]]==-1/.{U[{1,-Kerr}]->-ℰ,U[{4,-Kerr}]->ℒ,U[{3,-Kerr}]->UθEq},U[{2,-Kerr}]]]]

2. (g)

𝒦eq/.{θ[]->π/2,U[{3,-Kerr}]->0}//Simplify