Cadabra
Cadabra
Kasper Peeters
This book is available under the terms of the GNU Free Documentation License, version 1.2.
The Cadabra software is available under the terms of the GNU General Public License, version 3.
[email protected]
Contents
0
1 Introduction and overview 7
1.1 Bird’s eye overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2 Cadabra’s design philosophy . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.3 History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3 Mathematical properties 21
3.1 Derivatives and implicit dependence on coordinates . . . . . . . . . . . . . . 21
4 Manipulating expressions 23
4.1 Selecting parts of expressions . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.1.1 Zooming into an expression . . . . . . . . . . . . . . . . . . . . . . . 23
3
4.2 Using multiple files and notebooks . . . . . . . . . . . . . . . . . . . . . . . 24
4.2.1 Importing a notebook into another one . . . . . . . . . . . . . . . . . 24
4.2.2 Writing expressions to a file and reading them back . . . . . . . . . . 25
4.3 Default simplification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.4 Patterns, conditionals and regular expressions . . . . . . . . . . . . . . . . . 27
4.4.1 Conditionals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
6 Algorithms 39
6.1 Substitution and variation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
6.1.1 distribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
6.1.2 product rule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
6.1.3 substitute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6.1.4 vary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
6.1.5 expand power . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
6.1.6 unwrap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
6.1.7 integrate by parts . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.2 Metrics and bundles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
6.2.1 eliminate kronecker . . . . . . . . . . . . . . . . . . . . . . . . . . 47
6.2.2 eliminate metric . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.2.3 eliminate vielbein . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
6.2.4 einsteinify . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
6.2.5 epsilon to delta . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
6.2.6 expand delta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6.2.7 reduce delta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
6.3 Index manipulations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.3.1 combine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.3.2 explicit indices . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.3.3 lower free indices . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6.3.4 raise free indices . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
6.3.5 split index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
6.3.6 untrace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6.3.7 rename dummies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
6.3.8 rewrite indices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
6.3.9 expand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
6.4 Tensor component values . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6.4.1 complete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6.4.2 evaluate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.5 Factorisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.5.1 factor in . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.5.2 factor out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
6.6 Spinors and fermions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.6.1 expand diracbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.6.2 fierz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
6.6.3 join gamma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
6.6.4 sort spinors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
6.6.5 split gamma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
6.7 Sorting and canonicalisation . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
6.7.1 asym . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
6.7.2 canonicalise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
6.7.3 young project product . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.7.4 young project tensor . . . . . . . . . . . . . . . . . . . . . . . . . 70
6.7.5 meld . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
6.7.6 sort product . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
6.7.7 sort sum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
6.8 Weights and perturbations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
6.8.1 drop weight . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
6.8.2 keep weight . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
6.9 Simplification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
6.9.1 collect factors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
6.9.2 collect terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
6.9.3 map sympy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
6.9.4 simplify . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
6.10 Representations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
6.10.1 decompose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
6.10.2 decompose product . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
6.10.3 lr tensor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
6.11 Sub-expression manipulation . . . . . . . . . . . . . . . . . . . . . . . . . . 83
6.11.1 replace match . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
6.11.2 take match . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
6.11.3 zoom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5
6
Introduction and overview
1
1.1 Bird’s eye overview
Cadabra is a symbolic computer algebra system (CAS) designed to solve problems in
physics, in particular (but not limited to) those which deal with classical and quantum
field theory. Its input format is a subset of TeX, which means that throughout your com-
putations the maths will all stay in a form which is hopefully already familiar to you. It
has a large number of facilities which make it easy to work with tensors, anti-commuting
objects, implicit indices, implicit coordinate dependence and so on; things which help to
keep mathematical expressions compact and readable, so that your notebooks resemble
what you would do with pen-and-paper.
Cadabra is at its core a Python module (written in C++ and exposed to Python using
pybind11), which also contains a pre-parser which turns Cadabra’s language into pure
Python. You can use through a command line client, a graphical notebook interface, or
via Jupyter through the Cadabra kernel. You can, if you want to, also use it from Python
directly, or as a C++ library.
7
What we need the computer to do, in such a case, is to be good at performing simple but
tedious steps, without enforcing on the user how to do a particular computation. In other
words, we want the computer algebra system to be a scratchpad, leaving us in control of
which steps to take, not forcing us to return to a ’canonical’ expression at every stage.
Most existing computer algebra systems allow for this kind of work flow only by requiring
to stick clumsy ’inert’ or ’hold’ arguments onto expressions, by default always ’simplifying’
every input to some form they think is best. Cadabra starts from the other end of the
spectrum, and as a general rule keeps your expression untouched, unless you explicitly ask
for something to be done to it.
Another key issue in the design of symbolic computer algebra systems has always been
whether or not there should be a distinction between the ’data language’ (the language
used to write down mathematical expressions), the ’manipulation language’ (the language
used to write down what you want to do with those expressions) and the ’implementation
language’ (the language used to implement algorithms which act on mathematical expres-
sions). Many computer algebra systems take the approach in which these languages are
the same (Axiom, Reduce, Sympy) or mostly the same apart from a small core which uses
a different implementation language (Mathematica, Maple). The Cadabra project is rooted
in the idea that for many applications, it is better to keep a clean distinction between these
three languages. Cadabra writes mathematics using LaTeX, is programmable in Python,
and is under the hood largely written in C++.
1.3 History
Cadabra was originally written around 2001 to solve a number of problems related to
higher-derivative supergravity [1, 2]. It was then expanded and polished, and first saw
its public release in 2007 [3]. During the years that followed, it became clear that several
design decisions were not ideal, such as the use of a custom programming language and the
lack of functionality for component computations. Over the course of 2015-2016 a large
rewrite took place, which resulted in Cadabra 2.x [4]. This new version is programmable
in Python and does both abstract and component computations.
8
The input format
2
2.1 Input format
The input format of Cadabra is closely related to the notation used by LaTeX to denote
tensorial expressions. That is, one can use not only bracketed notation to denote child
objects, like in
object[child,child]
but also the usual sub- and superscript notation like
object^{child child}_{child}
One can use backslashes in the names of objects as well, just as in LaTeX. All of the symbols
that one enters this way are considered “passive”, that is, they will go into the expression
tree just like one has entered them.
Expressions are entered by using the ‘:=’ operator, as in
ex:=A+B+C_{m} C^{m};
A + B + Cm C m
Expressions (the ‘ex’ above) are ordinary Python objects (of type cadabra2.Ex), and their
names can thus only contain normal alphanumeric symbols.
type(ex);
<class ’cadabra2.Ex’>
9
Lines always have to be terminated with either a “;” or a “:”. These delimiting symbols act
in the same way as in Maple: the second form suppresses the output of the entered expres-
sion. Long expressions can, because of these delimiters, be spread over many subsequent
input lines. Any line starting with a “#” sign is considered to be a comment (even when
it appears within a multi-line expression). Comments are always ignored completely (they
do not end up in the expression tree. When entering maths as above (using the ‘:=’ assign-
ment operator) you do not need to indicate that the right-hand side is mathematics. There
are situations, however, when you do need to give Cadabra a hint that what you type is
maths, not Python. In this case, you add dollar symbols, just as in LaTeX,
substitute($A + B + C$, $C -> D$);
A+B+D
As you can see, this uses an ‘inline’ definition of a mathematical expression, without giving
it a name.
2.1.2 Algorithms
With basic use of Cadabra, you will typically display your expressions by postfixing them
with a semi-colon, as in
ex:=A_{m n} ( B^{n} + 3 C^{n} );
Amn (B n + 3C n )
What happens behind the scenes is that the semi-colon gets translated to a call of display
on the last-entered expression. It is therefore equivalent to
display(ex)
Amn (B n + 3C n )
If you do not want to display the expression, post-fix with a colon, as in
ex:=A_{m n} ( B^{n} + 3 C^{n} ):
If you want to display an expression again later, you can just write the name of the expres-
sion followed by a semi-colon, or use the display function again,
10
ex;
display(ex)
Amn (B n + 3C n )
Amn (B n + 3C n )
Note that while it may be tempting to use print(ex), the display function is better be-
cause it knows about the capabilities of the interface used, and it will automatically select
a text output when you use Cadabra from the terminal, or LaTeX output when you use it
in the graphical notebook.
Cadabra expressions are standard Python objects, and as such they have a __str__ method
which converts them into a printable expression, and a __repr__ method to produce a
machine readable form. These are called by the standard str and repr Python functions,
as the examples below show.
print(str(ex))
print(repr(ex))
\begin{verbatim}\prod(A_{m n})(\sum(B^{n})(3C^{n}))
\end{verbatim}
In addition there are some methods to obtain output useful in other software: Mathemat-
ica, LaTeX and Sympy:
print(ex.mma_form())
\begin{verbatim}A[DNm, DNn]*(B[UPn]+3*C[UPn])
\end{verbatim}
print(ex._latex_())
\begin{verbatim}A_{m n} \brwrap{(}{B^{n}+3C^{n}}{)}
\end{verbatim}
print(ex.sympy_form())
\begin{verbatim}A(DNm, DNn)*(B(UPn)+3*C(UPn))
\end{verbatim}
11
2.3 Object properties and declaration
Symbols in Cadabra have no a-priori “meaning”. If you write \Gamma, the program will not
know that it is supposed to be, for instance, a Clifford algebra generator. You will have to
declare the properties of symbols, i.e. you have to tell Cadabra explicitly that if you write
\Gamma, you actually mean a Clifford algebra generator. This indirect way of attaching a
meaning to a symbol has the advantage that you can use whatever notation you like; if you
prefer to write \gamma, or perhaps even \rho if your paper uses that, then this is perfectly
possible (object properties are a bit like “attributes” in Mathematica or “domains” in Axiom
and MuPAD).
Properties are all written using capitals to separate words, as in AntiSymmetric. This
makes it easier to distinguish them from commands. Properties of objects are declared by
using the “::” characters. This can be done “at first use”, i.e. by just adding the property to
the object when it first appears in the input. As an example, one can write
F_{m n p}::AntiSymmetric;
12
Attached property Indices(position=free) to [m#, n# ] .
the entire infinite set of objects m1, m2, m3, . . . and n1, n2, n3, . . . are declared to be in the
dummy index set “vector” (this way of declaring ranges of objects is similar to the “autode-
clare” declaration method of FORM). Properties can be assigned to an entire list of symbols
with one command, namely by attaching the property to the list. For example,
{n, m, p, q}::Integer(1..d);
13
{\psi_1, \psi_2, \psi_3}::Spinor;
ex:= \psi_1 + \psi_2 + \psi_3;
Γpoiuy
canonicalise(_);
− Γiopuy
The GammaMatrix property inherits from AntiSymmetric property, and therefore the
\Gamma object is automatically anti-symmetric in its indices. indices.
Some properties are not naturally associated to a single symbol or object, but have to
do with collections of them. A simple example of such a property is AntiCommuting. Al-
though it sometimes makes sense to say that “ψm is anticommuting” (meaning that ψm ψn =
−ψn ψm ), it happens just as often that you want to say “ψ and χ anticommute” (meaning
that ψχ = −χψ). The latter property is clearly relating two different objects.
Another example is dummy indices. While it may make sense to say that “m is a dummy
index”, this does not allow the program to substitute m with another index when a clash of
dummy index names occurs (e.g. upon substitution of one expression into another). More
useful is to say that “m, n, and p are dummy indices of the same type”, so that the program
can relabel a pair of m’s into a pair of p’s when necessary.
In Cadabra such properties are called “list properties”. You can associate a list property to
a list of symbols by simply writing, e.g. for the first example above,
{ \psi, \chi }::AntiCommuting;
14
2.3.3 Querying properties
For many built-in algorithms, assigning properties to the objects which appear in your
expressions will be enough to make them work. However, sometimes you may want to ex-
plicitly query whether a particular symbol has a particular property. The following example
shows how this works.
A_{m n}::Symmetric;
if Symmetric.get($A_{m n}$):
print("A_{m n} is symmetric.")
if AntiSymmetric.get($A_{m n}$):
print("A_{m n} is anti-symmetric.")
The object returned by Property.get(...) is either None or the property which you asked
about. It is possible to do something with that property, e.g. attach it to another symbol.
In the example below, we start off with one tensor with a symmetry, and then attach it to
another symbol.
A_{m n p}::TableauSymmetry(shape={1,1}, indices={1,2});
p = TableauSymmetry.get($A_{m n p}$)
p.attach($B_{m n p}$)
ex:= B_{m n p} - B_{m p n};
canonicalise(_);
15
will results in an error message. The location of indices is, by default, not considered to be
relevant. That is, you can write
{m, n}::Indices(name="free");
ex:=A_{m} + A^{m};
2Am
If, however, the position of an index means something (like in general relativity, where
index lowering and raising implies contraction with a metric), then you can declare index
positions to be “fixed”. This is done using
{a,b,c,d,e,f}::Indices(name="fixed", position=fixed);
16
ex:= G_{a b} Q;
rl:= Q-> F_{a b} F^{a b};
substitute(ex, rl);
Gab Q
Q → Fab F ab
Gab Fcd F cd
The a and b indices have automatically been relabelled to c and d in order to avoid a conflict
with the free indices on the Gab object. You may have noticed that when you write T_{a b}
the ‘a b’ in the subscript is not interpreted as a product, but rather as two different indices
to the tensor T .
a = Mb , ψγ m χ ,
where a and b are vectors, ψ and χ are spinors and M and γ m are matrices. Clearly, the
computer cannot know this without some further information. In Cadabra objects can
carry implicit indices, through the ImplicitIndex property. There are derived forms of
this, e.g. Matrix and Spinor. The following example shows how implicit indices ensure
that objects do not get moved through each other when sorting expressions.
{a,b}::ImplicitIndex;
M::Matrix;
ex:= a = M b;
sort_product(_);
17
different matrix which does not act on the same vector space. In other words, we consider
M i j cm n bj . Clearly we can also write this as M bc, which is indeed what sort_product
converts it to.
{i,j}::Indices(vector);
{m,n}::Indices(spinor);
M::ImplicitIndex(M^{i}_{j});
b::ImplicitIndex(b^{i});
c::ImplicitIndex(c^{m}_{n});
M cb
sort_product(_);
M bc
Such explicit property information is also respected by operators like Trace. The following
example shows how to remove objects from traces when they do not carry any indices on
which the trace acts.
Tr{#}::Trace(indices=vector);
ex:= Tr( M c M );
untrace(_);
It is possible to convert from implicit indices to explicit indices, that is, make Cadabra
write out all implicit indices explicitly. For this to work you need to have declared an
ImplicitIndex property which lists the explicit indices of the object. Cadabra will then
take care of creating index lines.
{i,j,k}::Indices;
a::ImplicitIndex(a^{i});
18
M::ImplicitIndex(M^{i}_{j});
ex:= M a;
M i j aj
Note how dummy indices were introduced automatically.
19
20
Mathematical properties
3
3.1 Derivatives and implicit dependence on coordinates
There is no fixed notation for derivatives; as with all other objects you have to declare
derivatives by associating a property to them, in this case the Derivative property.
\nabla{#}::Derivative;
∇Aµ
Or you can write the coordinate with respect to which the derivative is taken,
s::Coordinate;
A_{\mu}::Depends(s);
ex:=\nabla_{s}{ A_{\mu} };
21
Attached property Indices(position=free) to [µ, ν ] .
∇ ν Aµ
(in which case the first line is, for the purpose of using the derivative operator, actually un-
necessary). The main point of associating the Derivative property to an object is to make
the object obey the Leibnitz or product rule, as illustrated by the following example,
\nabla{#}::Derivative;
ex:= \nabla{ A_{\mu} * B_{\nu} };
product_rule(_);
22
Manipulating expressions
4
4.1 Selecting parts of expressions
In many other computer algebra systems, you can select parts of results using the mouse,
paste them into a new input cell, and then continue the computation. Naively this sounds
like a nice feature to have, and it is indeed quite useful for quick computations. However,
for larger projects, this feature quickly becomes a major source of trouble. Once you use
the cut-n-paste technique, you are no longer able to make any changes in cells before the
one with pasted content. Or rather, you can make changes, but they will not automatically
propagate to into the pasted cell. Any effect of the change at the top of the notebook
will have to be evaluated until the point of the cut-n-paste, and then you have to do the
cut-n-paste again by hand.
Now this is fine if you just do a quick computation, as you will probably know precisely
what you want to cut-n-paste. But if you give your notebook to someone else, this may no
longer be clear. Worse, if you do not look at your notebook for some time, and then return
after a few months (or years), you will most likely have forgotten completely what was the
logic for the particular cut.
For this reason, Cadabra does not support cut-n-paste of output. But that does not mean
that you cannot select parts of expressions for subsequent computation. For that, Cadabra
has a more systematic logic, which is built around the zoom and unzoom commands.
If you have a large expression, and want to select a part of it for further manipulation,
while temporarily ignoring the rest, use the zoom command. It takes an expression and a
23
pattern, and then suppresses all terms in the expression which do not match the pattern.
An example:
ex:= \int{ c A + c**2 B + c D + c**2 A }{x};
Z
cA + c2 B + cD + c2 A
dx
zoom(_, $c Q??$);
Z
(cA + . . . + cD + . . . ) dx
This has selected all terms with a single factor of c, and suppressed the other ones (but
keeping a reminder that those terms are still there, in the form of the dots). You can now
work on the visible terms as usual, e.g. doing a substitution,
substitute(_, $A -> E$);
Z
(cE + . . . + cD + . . . ) dx
As you can see, the substitution has only changed the terms which were visible at the
time.
The simplest way to separate functionality is to simply write a separate notebook with the
properties and expressions which you want to re-use elsewhere. In this way, writing a
24
‘package’ for Cadabra is nothing else but writing a separate notebook. You can import any
notebook into another one by using the standard Python import logic.
Example
Let us say we have a notebook library.cnb, which contains a single cell with the following
content:
{m,n,p,q,r}::Indices;
ex:=A_{q r} A_{q r};
You can now import this into another notebook by simply using
from library import *
Cadabra looks for the library.cnb notebook in your PYTHONPATH (just as in ordinary
Python programs), as well as in the current directory. You can see that this worked by
e.g. the following:
ex;
rename_dummies(ex);
Aqr Aqr
Amn Amn
Note that the import has thus not only imported the ex expression, but also the property
information about the index set, which enabled the rename_dummies to work. Behind the
scenes, what happens is that the import statement looks for a file library.cnb. If it finds
this, it will first convert that file to a proper Python file (remember the library.cnb file is
a Cadabra notebook, not a Python file). It then uses the standard Python logic to do the
import.
A somewhat more difficult way to re-use expressions is to write them to a file using stan-
dard Python methods, and then read them back elsewhere. This method is best used for
long computations of which you want to write an intermediate result out to disk, to be
read in later (instead of doing a re-computation). Be aware that if you write an expression
to disk, you do not write the property information of any of the symbols in that expression
to disk.
Example
The following example declares two expressions and writes them to disk. It then reads the
expressions back in again and assigns them to different Python names.
25
ex1:= A_{m n} \sin{x};
ex2:= B_{m n};
with open("output.cdb", "w") as file:
file.write( ex1.input_form()+"\n" )
file.write( ex2.input_form()+"\n" )
Amn sin x
Bmn
with open("output.cdb", "r") as file:
ex3=Ex( file.readline() )
ex4=Ex( file.readline() )
ex3;
ex4;
Amn sin x
Bmn
Note that when written in this way, the file output.cdb only contains the expressions, not
their names (ex1 and ex2 in the example above). Cadabra’s expressions can also be written
to disk using Python’s pickle functionality. This makes the code slightly less messy, but note
that the file will no longer be human-readable. If you use the pickle module, the example
above would read:
import pickle
Amn sin x
Bmn
with open("output.pkl", "rb") as file:
ex3=pickle.load(file)
ex4=pickle.load(file)
ex3;
ex4;
Amn sin x
Bmn
26
4.3 Default simplification
By default, Cadabra does very few things “by itself” with your expressions. It only collects
equal terms, but even that can be turned off if you want to. The logic is that all simplifica-
tion steps are contained in a function post_process, which is executed on every new input
and on every completion of an algorithm. It can contain arbitrary code, but by default it
reads
def post_process(ex):
collect_terms(ex)
which simply collects equal terms. You can for instance apply a substitution on every input
automatically,
def post_process(ex):
distribute(ex)
substitute(ex, $A_{m n} -> B_{m q} B_{q n}$)
collect_terms(ex)
{m,n,p,q}::Indices(vector);
A_{m n}::Symmetric;
ex:=A_{m n} ( A_{n m} + C_{n m});
27
Q+R
0
which matches all sums with two terms, each of which is a single symbol without indices or
arguments. If you want to match instead any object, with or without indices or arguments,
use the double question mark instead. To see the difference more explicitly, compare the
two substitute commands in the following example
ex:=A_{m n} + B_{m n};
substitute(_, $A? + B? -> 0$ );
substitute(_, $A?? + B?? -> 0$ );
Amn + Bmn
Amn + Bmn
0
Note that it does not make sense to add arguments or indices to object patterns; a con-
struction of the type A??_{\mu}(x) is meaningless and will be flagged as an error.
There is a special handling of objects which are dummy objects. Objects of this type do
not need the question mark, as their explicit name is never relevant. You can therefore
write
ex:= A_{m n};
substitute(_, $A_{p q}->0$);
Amn
0
to set all occurrances of the tensor A with two subscript indices to zero, regardless of the
names of the indices (as you can see, this command sets Apq to zero). When index sets
are declared using the Indices property, these will be taken into account when matching.
When replacing object wildcards with something else that involves these objects, use the
question mark notation also on the right-hand side of the rule. For instance,
ex:= C + D + E + F;
substitute(_, $A? + B? -> A? A?$, repeat=True);
C +D+E+F
CC + EE
replaces every consecutive two terms in a sum by the square of the first term. The following
example shows the difference between the presence or absence of question marks on the
right-hand side:
28
ex:= C + D;
substitute(_, $A? + B? -> A?$);
C
ex:= C + D;
substitute(_, $A? + B? -> A A$);
AA
So be aware that the full pattern symbol needs to be used on the right-hand side (in
contrast to many other computer algebra systems).
Note that you can also use patterns to remove or add indices or arguments, as in
{\mu, \nu, \rho, \sigma}::Indices(vector);
ex:= A_{\mu} B_{\nu} C_{\nu} D_{\mu};
substitute(_, $A?_{\rho} B?_{\rho} -> \dot{A?}{B?}$, repeat=True);
Aµ B ν C ν D µ
A · DB · C
4.4.1 Conditionals
Amn Bnq
Amn Bnq
Amn Bnq
Amn Bnq
29
Note that the conditional follows directly after the pattern, not after the full substitution
rule. A way to think about this is that the conditional is part of the pattern, not of the
rule. The reason why the conditional follows the full pattern, and not directly the symbol
to which it relates, is clear from the example above: the conditional is a “global” constraint
on the pattern, not a local one on a single index.
These conditions can be used to match names of objects using regular expressions. In the
following example, the pattern M? will match against C7,
ex:= A + B3 + C7;
substitute(_, $A + M? + N? | \regex{M?}{"[A-Z]7"} -> \sin(M? N?)/N?$);
A + B3 + C7
sin (C7B3 ) B3−1
Without the condition, the first match of M? would be against B3,
ex:= A + B3 + C7;
substitute(_, $A + M? + N? -> \sin(M? N?)/N?$);
A + B3 + C7
sin (B3C7 ) C7−1
30
Writing your own packages
5
5.1 Programming in Cadabra
Cadabra is fully programmable in Python. At the most basic level this means that you
can make functions which combine various Cadabra algorithms together, or write loops
which repeat certain Cadabra algorithms. At a more advanced level, you can inspect the
expression tree and manipulate individual subexpressions, or construct expressions from
elementary building blocks.
The two fundamental Cadabra objects are the Ex and the ExNode. An object of type Ex
represents a mathematical expression, and is what is generated if you type a line containing
:=, as in
ex:=A+B;
type(ex);
A+B
<class ’cadabra2.Ex’>
An object of type ExNode is best thought of as an iterator. It can be used to walk an
expression tree, and modify it in place (which is somewhat different from normal Python
iterators; a point we will return to shortly). The most trivial way to get an iterator is to call
the top member of an Ex object; think of this as returning a pointer to the topmost node
of an expression,
ex.top();
type(ex.top());
31
A + B
<class ’cadabra2.ExNode’>
You will also encounter ExNodes when you do a standard Python iteration over the elements
of an Ex, as in
for n in ex:
type(n);
display(n)
<class ’cadabra2.ExNode’>
A + B
<class ’cadabra2.ExNode’>
A
<class ’cadabra2.ExNode’>
B
As you can see, this ‘iterates’ over the elements of the expression, but in a perhaps some-
what unexpected way. We will discuss this in more detail in the next section. Important to
remember from the example above is that the ‘pointers’ to the individual elements of the
expression are ExNode objects. There are various other ways to obtain such pointers, using
various types of ‘filtering’, more on that below as well.
Once you have an ExNode pointing to a subexpression in an expression, you can query it
further for details about that subexpression.
ex:= A_{m n};
for i in ex.top().free_indices():
display(i)
Amn
m
n
The example above shows how, starting from an iterator which points to the top of the
expression, you can get a new iterator which can iterate over all free indices.
Before we continue, we should make a comment on how ExNode objects relate to Python
iterators. For many purposes, ExNode objects behave as you expect from Python iterators:
they allow you to loop over nodes of an Ex expression, you can call next(...) on them,
32
and so on. However, there are some slight differences, which have to do with the fact that
Cadabra wants to give you access to the nodes of the original Ex, so that you can modify
this original Ex in place. Consider for instance this example with a Python list of integers,
with standard iterators:
q=[1,2,3,4,5];
for element in q:
element=0
q;
1, 2, 3, 4, 5 1, 2, 3, 4, 5 It still produces the original list at the end of the day, because
each element is a copy of the element in the list. With ExNodes you can actually modify
the original Ex, as this example shows:
ex:=A + B + C + D;
for element in ex.top().terms():
element.replace($Q$)
ex;
A+B+C +D
Q+Q+Q+Q
In this case, element is not an Ex corresponding to each of the 5 terms, but rather an
ExNode, which is more like a pointer into the Ex object. The replace member function
allows you to replace the building blocks of the original ex expression.
If you want to get a proper Ex object (so a copy of the element in the expression over which
you are iterating), more like what you would get if iteration over Cadabra’s expressions
was an ordinary Python iteration, then you can use ExNode.ex():
ex:= A + 2 B + 3 C + 4 D;
lst=[]
for element in ex.top().terms():
lst.append( element.ex() )
ex;
lst[2];
A + 2B + 3C + 4D
A + 2B + 3C + 4D
3C
Here the list lst contains copies of the individual terms of the ex expression.
A good way to remember about this is to keep in mind that Cadabra tries its best to allow
you to modify expressions in-place. The ExNode iterators provide that functionality.
33
5.1.3 Traversing the expression tree
The ExNode iterator can be instructed to traverse expressions in various ways. The most
basic iterator is obtained by using standard Python iteration with a for loop,
ex:= A + B + C_{m} D^{m};
A + B + Cm Dm
for n in ex:
print(str(n))
\begin{verbatim}A + B + C_{m} D^{m}
A
B
C_{m} D^{m}
C_{m}
m
D^{m}
m
\end{verbatim}
The iterator obtained in this way traverses the expression tree node by node, and when you
ask it to print what it is pointing to, it prints the entire subtree of the node it is currently
visiting. If you are only interested in the name of the node, not the entire expression below
it, you can use the .name member of the iterator:
for n in ex:
print(str(n.name))
\begin{verbatim}\sum
A
B
\prod
C
m
D
m
\end{verbatim}
Often, this kind of ‘brute force’ iteration over expression elements is not very useful. A
more powerful iterator is obtained by asking for all nodes in the subtree which have a
certain name. This can be the name of a tensor, or the name of a special node, such as a
product or sum,
for n in ex["C"]:
display(n)
C_{m}
34
for n in ex["\\prod"]:
display(n)
C_{m} D^{m}
The above two examples used an iterator obtained directly from an Ex object. Various
ways of obtaining iterators over special nodes can be obtained by using member functions
of ExNode objects themselves. So one often uses a construction in which one first asks for
an iterator to the top of an expression, and then requests from that iterator a new one
which can iterate over various special nodes. The example below obtains an iterator over
all top-level terms in an expression, and then loops over its values.
for n in ex.top().terms():
display(n)
C_{m} D^{m}
Two special types of iterators are those which iterate only over all arguments or only over
all indices of a sub-expression. These are discussed in the next section.
There are various ways to obtain iterators which iterate over all arguments or all indices
of an expression. The following example, with a derivative acting on a product, prints the
argument of the derivative as well as all free indices.
\nabla{#}::Derivative;
ex:= \nabla_{m}{ A^{n}_{p} V^{p} };
∇m (An p V p )
for nabla in ex[r’\nabla’]:
for arg in nabla.args():
print(str(arg))
for i in nabla.free_indices():
print(str(i))
\begin{verbatim}A^{n}_{p} V^{p}
m
n
\end{verbatim}
35
5.1.5 Example: covariant derivatives
The following example shows how you might implement the expansion of a covariant
derivative into partial derivatives and connection terms.
def expand_nabla(ex):
for nabla in ex[r’\nabla’]:
nabla.name=r’\partial’
dindex = nabla.indices().__next__()
for arg in nabla.args():
ret:=0;
for index in arg.free_indices():
t2:= @(arg);
if index.parent_rel==sub:
t1:= -\Gamma^{p}_{@(dindex) @(index)};
t2[index]:= _{p};
else:
t1:= \Gamma^{@(index)}_{@(dindex) p};
t2[index]:= ^{p};
ret += Ex(str(nabla.multiplier)) * t1 * t2
nabla += ret
return ex
The sample expressions below show how this automatically takes care of not introducing
connections for dummy indices, and how it automatically handles indices which are more
complicated than single symbols.
\nabla{#}::Derivative;
ex:= 1/2 \nabla_{a}{ h^{b}_{c} };
expand_nabla(ex);
1
∇a hb c
2
1 b 1 b 1
∂a h c + Γ ap hp c − Γp ac hb p
2 2 2
ex:= 1/4 \nabla_{a}{ v_{b} w^{b} };
expand_nabla(ex);
1
∇a vb w b
4
1
∂a vb wb
4
36
ex:= \nabla_{\hat{a}}{ h_{b c} v^{c} };
expand_nabla(ex);
∇ba (hbc v c )
∂ba (hbc v c ) − Γp bab hpc v c
Basic use of the Cadabra C++ library consists of five parts: creating a kernel, inserting
object properties into the kernel, defining expressions, acting with algorithms on those
expressions, and displaying the result. A minimal example is as follows:
#include "cadabra2++.hh"
#include <iostream>
int main() {
Kernel k(true);
inject_property<AntiCommuting>(k, "{A,B}");
auto ex = "A B - B A"_ex(k);
sort_product sp(k, *ex);
sp.apply_generic();
collect_terms(k, *ex);
sp.apply_generic();
std::cout << pprint(k, ex) << std::endl;
}
37
(mostly), and the ex expression above thus changes as the code progresses.
Finally, the expression is printed by using the ‘pprint‘ function. This is necessary because
printing requires information stored in the Cadabra kernel.
38
Algorithms
6
6.1 Substitution and variation
6.1.1 distribute
a (b + c) → a b + a c .
a (b + c )
ab + ac
The algorithm in fact works on all objects which carry the Distributable property,
Op{#}::Distributable;
ex:=Op(A+B);
distribute(_);
39
The primary example of a property which inherits the Distributable property is
PartialDerivative. The distribute algorithm thus also automatically writes out par-
tial derivatives of sums as sums of partial derivatives,
\partial{#}::PartialDerivative;
ex:=\partial_{m}{A + B + C};
distribute(_);
Apply the product rule or “Leibnitz identity” to an object which has the Derivative prop-
erty, i.e.
D{#}::Derivative;
ex:=D(f g);
product_rule(_);
Dmn (f g )
product_rule(_);
Dm (Dn f g + f Dn g )
distribute(_);
Dm (Dn f g ) + Dm (f Dn g )
product_rule(_);
Dm Dn f g + Dn f Dm g + Dm f Dn g + f Dm Dn g
40
6.1.3 substitute
Generic substitution algorithm. Takes a rule or a set of rules according to which an expres-
sion should be modified. If more than one rule is given, it tries each rule in turn, until the
first working one is encountered, after which it continues with the next node.
ex:=G_{\mu \nu \rho} + F_{\mu \nu \rho};
substitute(_, $F_{\mu \nu \rho} -> A_{\mu \nu} B_{\rho}$ );
Gµνρ + Fµνρ
Gµνρ + Aµν Bρ
ex:= A_{\mu \nu} B_{\nu \rho} C_{\rho \sigma};
substitute(_, $A_{m n} C_{p q} -> D_{m q}$ );
am bn
substitute(_, $a_{q} -> c_{m n} d_{m n q}$ );
cqr dqrm bn
By postfixing a name with a question mark, it becomes a pattern. You do not need this
for indices (as the examples above show) but it is necessary for other types of function
arguments.
ex:= \sin{ x }**2 + 3 + \cos{ x }**2;
substitute(ex, $\sin{A?}**2 + \cos{A?}**2 = 1$);
sin x2 + 3 + cos x2
4
Substitute can match sub-products and sub-sums, and you do not have to specify terms or
factors in the order in which they appear,
ex:= A + B + C + D;
substitute(_, $A+C=Q$);
41
A+B+C +D
Q+B+D
ex:= A B C D;
substitute(_, $B D = Q$);
ABCD
AQC
However, you can request that the match is for the full sum or product,
ex:= A B C D + A B C D E F;
substitute(_, $A B C D = 1$, partial=False);
ABCD + ABCDEF
1 + ABCDEF
It will respect non-commuting objects and will not match if that would require moving
non-commuting objects through each other,
{Q,R,S,T}::NonCommuting;
ex:= Q R S T;
substitute(_, $S Q = 1$);
6.1.4 vary
Generic variation command. Takes a rule or a set of rules according to which the terms in
a sum should be varied. In every term, apply the rule once to every factor.
ex:= A B + A C;
AB + AC
DB + AC + DC + A (A − B )
It also works when acting on powers, in which case it will use the product rule to expand
them.
42
ex:= A**3;
vary(_, $A -> \delta{A}$);
A3
3A2 δ (A )
This algorithm is thus mostly intended for variational derivatives.
\partial{#}::PartialDerivative;
ex:= -\int{\partial_{\mu}{\phi} \partial^{\mu}{\phi} + m**2 \phi**2}{x};
integrate_by_parts(ex, $\delta{\phi}$);
Z
− δ (φ ) ∂µ µ φ − ∂ µ µ φδ (φ ) + 2m2 φδ (φ ) dx
−
canonicalise(_)
sort_product(_);
Z
− 2δ (φ ) ∂ µ µ φ + 2δ (φ ) φm2 dx
−
factor_out(_, $\delta{\phi}$);
Z
δ (φ ) − 2∂ µ µ φ + 2φm2 dx
−
Expand powers into repeated products, i.e. do the opposite of collect_factors. For ex-
ample,
ex:=(A B)**3;
43
(AB )3
expand_power(_);
ABABAB
sort_product(_);
AAABBB
collect_factors(_);
A3 B 3
This command automatically takes care of index relabelling when necessary, as in the
following example
{m,n,p,q,r}::Indices(vector).
ex:= (A_m B_m)**3;
(Am Bm )3
expand_power(_);
Am Bm An Bn Ap Bp
6.1.6 unwrap
Move objects out of Derivatives, Accents or exterior (wedge) products when they do not
depend on these operators. The most basic example is Accents, which will get removed
from objects which do not depend on them, as in the following example:
\hat{#}::Accent;
\hat{#}::Distributable;
B::Depends(\hat);
ex:=\hat{A+B+C};
A+
\ B+C
distribute(_);
44
A
b+B
b+C
b
unwrap(_);
B
b
Derivatives will be set to zero if an object inside does not depend on it. All objects which
are annihilated by the derivative operator are moved to the front (taking into account
anti-commutativity if necessary),
\partial{#}::PartialDerivative;
{A,B,C,D}::AntiCommuting;
x::Coordinate;
{B,D}::Depends(\partial{#});
∂x (ABCD )
unwrap(_);
− AC∂x (BD )
Note that a product remains inside the derivative; to expand that use prodrule. Here is
another example:
\del{#}::LaTeXForm("\partial").
\del{#}::Derivative;
X::Depends(\del{#});
ex:=\del{X*Y*Z};
∂XY Z + X∂Y Z + XY ∂Z
unwrap(_);
∂XY Z
45
Note that all objects are by default constants for the action of Derivative operators. If
you want objects to stay inside derivative operators you have to explicitly declare that they
depend on the derivative operator or on the coordinate with respect to which you take a
derivative.
The final case where unwrap acts is when exterior products contain factors which are
scalars (or forms of degree zero). The following example shows this.
{f,g}::DifferentialForm(degree=0).
{V, W}::DifferentialForm(degree=1).
{V,g}::AntiCommuting;
foo := f V ^ W g;
− f gV ∧ W
As this example shows, unwrap takes into account commutativity properties (hence the
sign flip).
Integrate by parts. This requires an expression with an object carrying a Derivative prop-
erty. The algorithm should be given an expression that any derivatives should be integrated
away from. An example makes this more clear:
\partial{#}::PartialDerivative;
ex:= \int{ \partial_{m}{ A } B C D }{x};
integrate_by_parts(_, $A$);
Z
− A∂m (BCD ) dx
product_rule(_);
46
Z
− A (∂m BCD + B∂m CD + BC∂m D ) dx
distribute(_);
Z
− (A∂m BCD + AB∂m CD + ABC∂m D ) dx
Note that integrate_by_parts only does the formal manipulation of moving the deriva-
tive around. If you want to discard derivatives of objects which are constant, you need
to use the Depends property to indicate on which coordinates or derivatives objects de-
pend, and the unwrap algorithm to eliminate derivatives of constants, as in the following
lines.
{B,D}::Depends(\partial);
Eliminates Kronecker delta symbols by performing index contractions. Also replaces con-
tracted Kronecker delta symbols with the range over which the index runs, if known.
\delta_{m n}::KroneckerDelta.
ex:=A_{m p} \delta_{p q} B_{q n};
eliminate_kronecker(_);
47
δpq δpq
d
In order to eliminate metric factors (i.e. to ‘raise’ and ’lower’ indices) use the algorithm
eliminate_metric.
gmp g pm
gp p
eliminate_kronecker(_);
10
Related algorithms are eliminate_kronecker and eliminate_vielbein. It is sometimes
useful to eliminate only those metrics which have two dummy indices (so as to avoid
changing indices on non-metric factors), as in the following example:
{a,b,c,d,e,f}::Indices(position=fixed);
g_{a b}::Metric;
g^{a b}::InverseMetric;
ex:=X_{a} g^{a b} g_{b c} g^{c d} g_{d e} g^{e f};
eliminate_metric(ex, repeat=True, redundant=True);
48
Without the redundant=True option, this would have reduced the expression to X f .
Indices of one type can be converted to another type by using a vielbein or inverse vielbein
object.
{ m, n, p }::Indices(flat).
{ \mu, \nu, \rho }::Indices(curved).
e^{m}_{\mu}::Vielbein.
ex:= H_{m n p} e^{m}_{\mu} e^{p}_{\rho};
eliminate_vielbein(_, repeat=True);
Hmnp em µ ep ρ
Hµnρ
This is similar to eliminate_metric.
6.2.4 einsteinify
In an expression containing dummy indices at the same position (i.e. either both subscripts
or both superscripts), raise or lower one of the indices.
ex:= A_{m} A_{m};
einsteinify(_);
Am Am
Am Am
ex:= A^{m} A^{m};
einsteinify(_);
Am Am
Am Am
If an additional argument is given to this algorithm, it instead inserts “inverse metric”
objects, with the name as indicated by the additional argument.
{m,n}::Indices.
ex:= A_{m} A_{m};
einsteinify(_, $\eta$);
49
Am Am
Am An η mn
ex:= A^{m} A^{m};
einsteinify(_, $\eta$);
Am Am
Am An η mn
Note that the second form requires that there are enough dummy indices defined through
the use of Indices.
Replace a product of two epsilon tensors with a generalised delta according to the expres-
sion
1
r1 ···rd s1 ···sd = p εr1 ···rd |g|εs1 ···sd = sign(g) d! δsr11···s
···rd
p
d
, (6.1)
|g|
where sign(g) denotes the signature of the metric g used to raise/lower the indices (see
EpsilonTensor for conventions on the epsilon tensor). When the indices are not ocurring
up/down as in this expression, and the index position is not free, metric objects will be
generated instead.
Here is an example:
{a,b,c,d}::Indices.
{a,b,c,d}::Integer(1..3).
\delta{#}::KroneckerDelta.
\epsilon_{a b c}::EpsilonTensor(delta=\delta).
ex:=\epsilon_{a b c} \epsilon_{a b d};
abc abd
epsilon_to_delta(_);
2δcd
Remember that if the result is a generalised delta, you can expand it in terms of normal
deltas using expand_delta,
50
ex:=\epsilon_{a b c} \epsilon_{a d e};
epsilon_to_delta(_);
expand_delta(_);
abc ade
2δbdce
δbd δce − δcd δbe
In order for this algorithm to work, you need to make sure that the epsilon symbols in
your expression are declared as EpsilonTensor and that these declarations involve a spec-
ification of the delta and/or metric symbols. As you can see from this example, con-
tracted indices inside the generalised delta are automatically eliminated, as the algorithm
reduce_gendelta is called automatically; if you do not want this use the optional argu-
ment reduce=False.
ex:=\epsilon_{a b c} \epsilon_{a b d};
epsilon_to_delta(_, reduce=False);
abc abd
6δaabbcd
Note that the results typically depend on the signature of the space-time, which can be in-
troduced through the optional metric argument of the EpsilonTensor property. Compare
the two examples below:
{a,b,c,d}::Indices.
{a,b,c,d}::Integer(1..3).
\delta{#}::KroneckerDelta.
\epsilon_{a b c}::EpsilonTensor(delta=\delta, metric=g_{a b}).
g_{a b}::Metric(signature=-1).
ex:=\epsilon_{a b c} \epsilon_{a b c};
abc abc
epsilon_to_delta(_);
−6
g_{a b}::Metric(signature=+1).
ex:=\epsilon_{a b c} \epsilon_{a b c};
epsilon_to_delta(_);
abc abc
6
51
Note that you need to specify the full symbol for the metric, including the indices, whereas
the Kronecker delta argument only requires the name without the indices (because a
contraction can generate generalised Kronecker delta symbols with any number of in-
dices).
δa b c d
expand_delta(_);
1 a c 1
δ bδ d − δc bδa d
2 2
ex:=\delta^{a}_{m}^{l}_{n} \delta_{a}^{c}_{b}^{d};
δ a m l n δa c b d
expand_delta(_);
distribute(_);
eliminate_kronecker(_);
canonicalise(_);
1 a l 1 1 c d 1 c d
δ mδ n − δl mδa n δa δb − δb δa
2 2 2 2
1 a l 1 1 1
δ m δ n δa c δb d − δ a m δ l n δb c δa d − δ l m δ a n δa c δb d + δ l m δ a n δb c δa d
4 4 4 4
1 l 1 1 1
δ n δm c δb d − δ l n δb c δm d − δ l m δn c δb d + δ l m δb c δn d
4 4 4 4
1 d c l 1 1 1
δb δ m δ n − δb c δ d m δ l n − δb d δ c n δ l m + δb c δ d n δ l m
4 4 4 4
52
Note that it is in principle possible to get a result similar to the expanded form by using
the Young projector and then canonicalising, but this is more expensive:
ex:=\delta^{a}_{b}^{c}_{d};
δa b c d
young_project_tensor(_);
δa b c d
m
hY
···an b1 i a ···a
n! δba11···b n
δa1 · · · δabm
m
= d − (n − i) m+1
(n − m)! δbm+1 n
···bn . (6.2)
i=1
Here is an example:
\delta{#}::KroneckerDelta;
{m,n,q}::Integer(0..3);
ex:=\delta_{m}^{n}_{n}^{q};
δm n n q
reduce_delta(_);
3
− δm q
2
Note that this requires that the indices on the Kronecker delta symbol also carry an Integer
property to specify their range.
53
6.3 Index manipulations
6.3.1 combine
Combine two consecutive objects with indexbrackets and consecutive contracted indices
into one object with an indexbracket. An example with two contracted matrices:
ex:=(\Gamma_r)_{\alpha\beta} (\Gamma_{s t u})_{\beta\gamma};
combine(_);
(Γr ) αβ (Γstu ) βγ
(Γr Γstu ) αγ
(Γr ) αβ vβ
(Γr v ) α
In Cadabra you can write expressions which are understood to have indices suppressed,
in order to get a cleaner notation. This is often used for vector/matrix notation, or
when dealing with spinors. In order to inform Cadabra about these implicit indices, you
use the ImplicitIndex property (which is also necessary to prevent Cadabra from mov-
ing these objects through each other when sorting products into canonical form). The
explicit_indices algorithm can then make these indices explicit, which can sometimes
make them easier to work with, for example when doing substitutions. In the following ex-
ample we define two sets of indices, and several objects which are assumed to have implicit
indices.
{m,n,p}::Indices(spacetime, position=fixed);
{a,b,c,d,e,f,g,h}::Indices(spinor, position=fixed);
\sigma^{p}::ImplicitIndex(\sigma^{p a}_{b});
\psi::ImplicitIndex(\psi_{a});
54
\chi::ImplicitIndex(\chi^{a});
ψσ m σ n χ
We can now make the indices explicit using
explicit_indices(ex);
ψa σ ma b σ nb c χc
This also works when there are trace operators, as is illustrated in the following exam-
ple.
Tr{#}::LaTeXForm("{\rm Tr}").
Tr{#}::Trace(indices=spinor);
ex:= Tr(\sigma^{m} \sigma^{n} + \sigma^{n} \sigma^{m});
σ ma b σ nb a + σ na b σ mb a
Free indicies (indices declared with the Indices(position=free) property) can appear
as subscripts or superscripts, but sometimes it is useful to move them all into the same
position.
{a,b,c}::Indices(name=A, position=free);
{m,n,p}::Indices(name=B, position=fixed);
ex:=A_{a b m} B^{a b m};
55
Attached property Indices(position=free) to [a, b, c ] .
Attached property Indices(position=fixed) to [m, n, p ] .
Aabm B abm
lower_free_indices(_);
Aabm Bab m
The opposite of this is raise_free_indices, which moves all indices to be super-
scripts.
Free indicies (indices declared with the Indices(position=free) property) can appear
as subscripts or superscripts, but sometimes it is useful to move them all into the same
position.
{a,b,c}::Indices(name=A, position=free);
{m,n,p}::Indices(name=B, position=fixed);
ex:=A_{a b m} B^{a b m};
Aab m B abm
The opposite of this is lower_free_indices, which moves all free indices to be sub-
scripts.
56
{M,N,P,Q,R}::Indices(full).
{m,n,p,q,r}::Indices(space1).
{a,b,c,d,e}::Indices(space2).
ex:=A_{M p} B_{M p};
split_index(_, $M,m,a$);
AM p B M p
Amp Bmp + Aap Bap
ex:=A_{M p} B_{M p};
split_index(_, $M,m,4$);
AM p B M p
Amp Bmp + A4p B4p
Note that the two index types into which the original indices should be split can be either
symbolic (as in the first case above) or numeric (as in the second case).
6.3.6 untrace
When a trace contains objects which do not carry any implicit indices on which the trace
acts, the untrace algorithm can be used to take them out of the trace. This is similar to
the way in which unwrap takes objects out of derivatives when they do not depend on the
object with respect to which the derivative is taken. Unless you declare objects to have a
ImplicitIndex property, they will be taken out. The minimal example does not specify
these indices, e.g.
{A,B}::ImplicitIndex.
tr{#}::Trace.
ex:= tr( q A B );
untrace(_);
tr (qAB )
qtr (AB )
In the declaration of a trace, it is possible to indicate over which indices the trace is being
taken.
{a,b,c}::Indices(spinor).
{m,n,p}::Indices(vector).
C::ImplicitIndex(C_{a b}).
D::ImplicitIndex(D_{a b}).
57
E::ImplicitIndex(E^{m n}).
Tr{#}::Trace(indices=spinor).
ex:= Tr( C D E );
T r (CDE )
untrace(_);
ET r (CD )
Note how, even though E has implicit indices, it has been moved out of the trace, as the
latter is declared to be a trace over spinor indices, not vector indices.
Rename the dummy indices in an expression. This can be necessary in order to make vari-
ous terms in a sum use the same names for the indices, so that they can be collected.
{m,n,p,q,r,s}::Indices(vector);
ex:=A_{m n} B_{m n} - A_{p q} B_{p q};
0
Note that the indices need to have been declared as being part of an index list, using the
Indices property.
The algorithm can also be used to rename dummies from one set to another one, e.g.
to change index conventions (this is used in many of Cadabra’s packages). Here is an
example.
{m,n,p,q}::Indices("one");
{a,b,c,d}::Indices("two");
ex:= A_{m} A^{m} + B_{m} C^{m} + A_{n} A^{n} + Q_{c d} R^{d c};
58
Attached property Indices(position=free) to [m, n, p, q ] .
Am Am + Bm C m + An An + Qcd Rdc
The above expression has indices in two different sets. We now rename the first set to the
second,
rename_dummies(_, "one", "two");
Rewrite indices on an object by contracting it with a second object which contains indices
of both the old and the new type (a vielbein, in other words, or a metric). A vielbein
example is
{m,n,p}::Indices(flat).
{\mu,\nu,\rho}::Indices(curved).
ex:=T_{m n p};
rewrite_indices(_, $T_{\mu\nu\rho}$, $e_{\mu}^{n}$);
Tmnp
Tµνρ eµ m eν n eρ p
If you want to raise or lower an index with a metric, this can also be done with as an index
rewriting command, as the following example shows:
{\mu,\nu,\rho,\sigma,\lambda,\kappa}::Indices(curved, position=fixed).
ex:=H_{\mu \nu \rho};
rewrite_indices(_, $H^{\mu \nu \rho}$, $g_{\mu \nu}$);
Hµνρ
As these examples show, the desired form of the tensor should be given as the first argu-
ment, and the conversion object (metric, vielbein) as the second object.
59
6.3.9 expand
Write out products of matrices and vectors inside indexbrackets, inserting new dummy
indices for the contraction. This requires that the objects inside the index bracket are
properly declared to have Matrix or ImplicitIndex properties.
Here is an example with multiple matrices:
{a,b,c,d,e}::Indices;
{A,B,C,D}::Matrix;
expand(_);
(ABQD ) ab
expand(_);
(Γr v ) α
60
expand(_);
(Γr ) αβ vβ
6.4.1 complete
Complete a set of substitution rules with additional rules based on the properties of the
objects appearing in the rules.
This can for instance be used to generate rules for the inverse components of the metric
given the rules for the metric components themselves, as in the example below.
Note that the argument itself gets modified (amended) with the additional rules.
{r,t}::Coordinate.
{m,n,p,q}::Indices(values={r,t}).
g_{m n}::Metric.
g^{m n}::InverseMetric.
r2 r2
(gtt = r, gtr = , grt = , grr = 1)
a a
complete(rl, $g^{m n}$);
r2 r2 r4 r2
(gtt = r, gtr = , grt = , grr = 1, g rr = 1 + , g rt = − , g tr =
a a 4 r4
a2 r − ar 2 a r− a2
r2 1
− , g tt = r4
)
r4 r− a2
a r− a2
Note that this uses SymPy behind the scenes to do the scalar algebra and matrix inver-
sion.
61
6.4.2 evaluate
Given an abstract tensor expression and a set of rules for the components of tensors occur-
ring in this expression, evaluate the components of the full expression.
The minimal information needed for this to work is a declaration of the indices used, and
a declaration of the values that those indices use:
{r,t}::Coordinate.
{m,n,p,s}::Indices(values={t,r}).
ex:= A_{n m} B_{m n p} ( C_{p s} + D_{s p} );
r =6r2 t + 6
t =15t2
6.5 Factorisation
6.5.1 factor in
Given a list of symbols, this algorithm collects terms in a sum that only differ by pre-factors
consisting of these given symbols. As an example,
ex:=a b + a c + a d;
ab + ac + ad
62
factor_in(_, $b,c$);
(b + c ) a + ad
The name is perhaps most easily understood by thinking of it as a complement to
factor_out. Or in case you are familiar with FORM, factor_in is like its antibracket
statement.
The algorithm of course also works with indexed objects, as in
ex:=A_{m} B_{m} + C_{m} A_{m};
factor_in(_, $B_{n}, C_{n}$);
Am B m + C m Am
Am B m + C m Am
(not yet finished)
Given a list of symbols, this algorithm tries to factor those symbols out of terms. As an
example,
ex:= a b + a c e + a d;
ab + ace + ad
factor_out(_, $a$);
a (b + ce + d )
If you have non-commuting objects and want to factor out to the right, use the right=True
option, as in
{A,B,C,D}::NonCommuting;
ex:= A B C D + B A C D;
factor_out(ex, $D$, right=True);
63
ex:= a b + a c e + a c + c e + c d + a d;
ab + ace + ac + ce + cd + ad
factor_out(_, $a, c$);
a (b + d ) + ac (e + 1 ) + c (e + d )
That is, separate terms will be generated for terms which differ by powers of the factors to
be factored out.
The algorithm of course also works with indexed objects, as in
ex:= A_{m} B_{m} + C_{m} A_{m};
Am Bm + Cm Am
factor_out(_, $A_{m}$);
Am (Bm + Cm )
Rewrite the Dirac conjugate of a product of spinors and gamma matrices as a product of
Dirac and hermitean conjugates. This uses
ψ = iψ † Γ0 , (6.3)
together with
Γ†m = Γ0 Γm Γ0 . (6.4)
For example,
\bar{#}::DiracBar.
\psi::Spinor(dimension=10).
\Gamma{#}::GammaMatrix.
ex:=\bar{\Gamma^{m n p} \psi};
Γmnp ψ
expand_diracbar(_);
ψΓmnp
64
6.6.2 fierz
Change the order of the spinors in a four-spinor expression using a Fierz transformation.
This relies on the generic fact that Dirac gamma matrices satisfy the completeness rela-
tion
X
(Γa )ij (Γa )kl = δil δjk .
a
1 1 1
− θΓm Γm λψχ − θΓm Γn Γm λψΓn χ − θΓm Γnp Γm λψΓpn χ
4 4 8
The argument to fierz is the required order of the fermions; note that this algorithm does
not flip around Majorana spinors and sort_spinors should be used for that. Also note
that it is important to define not only the symbols representing the spinors, Dirac bar and
gamma matrices, but also the range of the indices.
65
Join two fully anti-symmetrised gamma matrix products according to the expression
min(n,m)
b1 ...bn
X n!m!
Γ Γa1 ...am = Γ[b1 ...bn−p [ap+1 ...am η bn−p+1 ...bn ] a1 ...am−p ] . (6.5)
(n − p)!(m − p)!p!
p=0
Γmn Γp
Γmnp + 2Γm gnp
with
\Gamma{#}::GammaMatrix(metric=g).
ex:= \Gamma_{m n} \Gamma_{p};
join_gamma(ex, expand=True);
Γmn Γp
Γmnp + Γm gnp − Γn gmp
Note that the gamma matrices need to have a metric associated to them in order for this
algorithm to work. In order to reduce the number of terms somewhat, one can instruct the
algorithm to make use of generalised Kronecker delta symbols in the result; these symbols
are defined as
δ r1 s1 r2 s2 · · · rn sn = δ [r1 s1 δ r2 s2 · · · rn ] sn . (6.6)
Anti-symmetrisation is implied in the set of even-numbered indices. The use of these sym-
bols is triggered by the use_gendelta option,
{m,n,p,q}::Indices(position=fixed).
\Gamma{#}::GammaMatrix(metric=\delta).
ex:=\Gamma_{m n} \Gamma^{p q};
join_gamma(_, use_gendelta=True);
Γmn Γpq
Γmn pq + Γm q δn p − Γm p δn q − Γn q δm p + Γn p δm q + 2δn p m q
66
6.6.4 sort spinors
Sorts Majorana spinor bilinears using the Majorana flip property, which for anti-commuting
spinors takes the form
1
ψ 1 Γr1 ···rn ψ2 = αβ n (−) 2 n(n−1) ψ 1 Γr1 ···rn ψ2 . (6.7)
Here is an example.
{\chi, \psi, \psi_{m}}::Spinor(dimension=10, type=MajoranaWeyl).
{\chi, \psi, \psi_{m}}::AntiCommuting.
\bar{#}::DiracBar.
\Gamma{#}::GammaMatrix.
{\psi_{m}, \psi, \chi}::SortOrder.
ex:=\bar{\chi} \Gamma_{m n} \psi;
χΓmn ψ
sort_spinors(_);
− ψΓmn χ
Γm Γnp − Γp η mn + Γn η mp
67
6.7 Sorting and canonicalisation
6.7.1 asym
ABC
asym(_, $A,B,C$);
1 1 1 1 1 1
ABC − ACB − BAC + BCA + CAB − CBA
6 6 6 6 6 6
When used with indices, remember to also indicate whether you want to symmetrise upper
or lower indices, as in the example below.
ex:=A_{m n} B_{p};
Amn Bp
asym(_, $_{m}, _{n}, _{p}$);
1 1 1 1 1 1
Amn Bp − Amp Bn − Anm Bp + Anp Bm + Apm Bn − Apn Bm
6 6 6 6 6 6
If you want to symmetrise in the indicated objects instead, use the antisymmetric=False
flag:
ex:=A_{m n} B_{p};
asym(_, $_{m}, _{n}, _{p}$, antisymmetric=False);
Amn Bp
1 1 1 1 1 1
Amn Bp + Amp Bn + Anm Bp + Anp Bm + Apm Bn + Apn Bm
6 6 6 6 6 6
6.7.2 canonicalise
68
Canonicalise a product of tensors, using the mono-term index symmetries of the individual
tensors and the exchange symmetries of identical tensors. Tensor exchange takes into
account commutativity properties of identical tensors.
Note that this algorithm does not take into account multi-term symmetries such as
the Ricci identity of the Riemann tensor; those canonicalisation procedures require the
use of young\_project\_tensor or young\_project\_product. Similarly, dimension-
dependent identities are not taken into account, use decompose_product for those.
In order to specify symmetries of tensors you need to use symmetry properties such
as Symmetric, AntiSymmetric or TableauSymmetry. The following example illustrates
this.
A_{m n}::AntiSymmetric.
B_{p q}::Symmetric.
ex:=A_{m n} B_{m n};
canonicalise(_);
Amn Bmn
0
If the various terms in an expression use different index names, you may need an additional
call to rename\_dummies before the terms get collected together:
{m,n,p,q,r,s}::Indices.
A_{m n}::AntiSymmetric.
C_{p q r}::AntiSymmetric.
ex:=A_{m n} C_{m n q} + A_{s r} C_{s q r};
canonicalise(_);
0
If you have symmetric or anti-symmetric tensors with many indices, it sometimes pays off
to sort them to the end of the expression (this may speed up the canonicalisation process
considerably).
69
Project all tensors in a product with their Young tableau projector. Each factor is projected
in turn, after which the product is distributed and then canonicalised. This is often faster
and more memory-efficient than first projecting every factor and then distributing.
Young projection can be used to find equalities between tensor polynomials which are due
to multi-term symmetries, such as the Ricci identity in the example below.
{a,b,c,d}::Indices.
R_{a b c d}::RiemannTensor.
Project tensors with their Young projection operator. This works for simple symmetric or
anti-symmetric objects, as in
A_{m n}::Symmetric.
ex:= A_{m n} A_{m p};
Amn Amp
young_project_tensor(_);
1 1 1 1
Amn + Anm Amp + Apm
2 2 2 2
but more generically works for any tensor which has a TableauSymmetry property attached
to it.
A_{m n p}::TableauSymmetry(shape={2,1}, indices={0,2,1}).
ex:= A_{m n p};
Amnp
young_project_tensor(_);
1 1 1 1
Amnp + Apnm − Anmp − Apmn
3 3 3 3
70
When the parameters modulo_monoterm is set to True, the resulting expression will be
simplified using the monoterm symmetries of the tensor,
A_{m n p}::TableauSymmetry(shape={2,1}, indices={0,2,1}).
ex:= A_{m n p};
Amnp
young_project_tensor(_, modulo_monoterm=True);
2 1 1
Amnp − Anpm + Ampn
3 3 3
(in this example, the tensor is anti-symmetric in the indices 0 and 1, hence the simplifica-
tion compared to the previous example).
6.7.5 meld
In a sum of terms, combine terms using mono-term and multi-term symmetries such that
the expression does not use an overcomplete basis. The meld algorithm does not rewrite
the expression to a canonical form, but it instead combines terms such that no terms re-
main which are a linear combination of the other terms. It can hence be used to prove
equivalency of expressions under both mono-term and multi-term symmetries. A typical
use cases where meld is preferable over e.g. canonicalise is when the expression contains
tensors with multi-term symmetries:
R_{a b c d}::RiemannTensor;
ex:=R_{a b c d}R_{a b c d} + R_{a b c d}R_{a c b d};
meld(ex);
3
Rabcd Rabcd
2
What has happened here is that the algorithm figured out that the first term is expressible
in terms of the second, and has combined the two. If you write the terms in the opposite
order, meld still combines them, but now in the form of the other term:
ex:=R_{a b c d}R_{a c b d}+ R_{a b c d}R_{a b c d};
meld(ex);
71
3Rabcd Racbd
So meld does not canonicalise, but rather writes the expression such that there remain
no linear dependencies between terms. This algorithm can of course be used for simpler
situations, e.g. one which uses mono-term symmetries only:
A_{m n}::AntiSymmetric;
ex:=A_{m n} - A_{n m};
meld(ex);
2Qmn Rmn
The algorithm also handles cyclic symmetries of traces:
{\mu,\nu}::Indices(vector).
u^{\mu}::ImplicitIndex.
u^{\mu}::SelfNonCommuting.
tr{#}::Trace.
ex := tr{u^{\mu} u^{\mu} u^{\nu} u^{\nu}} -
tr{u^{\mu} u^{\nu} u^{\nu} u^{\mu}};
meld(ex);
tr (uµ uµ uν uν ) − tr (uµ uν uν uµ )
0
Sort factors in a product, taking into account any SortOrder properties. Also takes into
account commutativity properties, such as SelfCommuting. If no sort order is given, it first
does a lexographical sort based on the name of the factor, and if two names are identical,
does a sort based on the number of children and (if this number is equal) a lexographical
72
comparison of the names of the children. Symbols starting with a backslash (greek letters
etc.) get sorted to the right of roman letters.
The simplest sort is illustrated below,
ex := C B A D;
sort_product(_);
CBAD
ABCD
We can declare the objects to be anti-commuting, which then leads to
{A, B, C, D}::AntiCommuting.
ex := C B A D;
sort_product(_);
CBAD
− ABCD
For indexed objects, the anti-commutativity of components is indicated using the
SelfAntiCommuting property,
\psi_{m}::SelfAntiCommuting.
ex := \psi_{n} \psi_{m} \psi_{p};
sort_product(_);
ψn ψm ψp
− ψm ψn ψp
Finally, the lexographical sort order can be overridden by using the SortOrder prop-
erty,
{D, C, B, A}::SortOrder.
{A, B, C, D}::AntiCommuting.
ex := C B A D;
sort_product(_);
CBAD
− DCBA
73
Sort terms in a sum, taking into account any SortOrder properties, or else sorting lexo-
graphically.
ex:=E+D+A+C+B;
E+D+A+C +B
sort_sum(_);
A+B+C +D+E
This is often useful in case sums appear as exponents; in this case it is necessary to first
sort the sums before terms can be collected, as the following example shows.
ex:=a**(-1+d) - a**(d-1);
a(−1+d) − a(d−1)
sort_sum(_);
Drop those terms for which a product has the indicated weight. Weights are computed
by making use of the Weight property of symbols. This algorithm does the opposite of
keep_weight.
As an example, consider the simple case in which we want to drop all terms with 3 fields.
This is done using
{A,B}::Weight(label=field);
ex:=A B B + A A A + A B + B;
AB + B
However, you can also do more complicated things by assigning non-unit weights to sym-
bols, as in the example below,
74
{A,B}::Weight(label=field);
C::Weight(label=field, value=2);
ex:=A B B + A A A + A B + A C + B:
AB + B
Weights can be “inherited” by operators by using the WeightInherit property. Here is an
example using partial derivatives,
{\phi,\chi}::Weight(label=small, value=1);
\partial{#}::PartialDerivative;
\partial{#}::WeightInherit(label=all, type=multiplicative);
ex:=\phi \partial_{0}{\phi} + \partial_{0}{\lambda} + \lambda \partial_{3}{\chi
};
φ∂0 φ + ∂0 λ
Keep only those terms for which a product has the indicated weight. Weights are computed
by making use of the Weight property of symbols. This algorithm does the opposite of
drop_weight.
As an example, consider the simple case in which we want to keep all terms with 3 fields.
This is done using
{A,B}::Weight(label=field);
ex:=A B B + A A A + A B + B;
keep_weight(_, $field=3$);
75
ABB + AAA + AB + B
ABB + AAA
However, you can also do more complicated things by assigning non-unit weights to sym-
bols, as in the example below,
{A,B}::Weight(label=field);
C::Weight(label=field, value=2);
ex:= A B B + A A A + A B + A C + B;
ABB + AAA + AC
Weights also apply to tensorial expressions. Consider e.g. a situation in which we have a
polynomial of the type
ex:=c^{a} + c^{a}_{b} x^{b} + c^{a}_{b c} x^{b} x^{c} + c^{a}_{b c d} x^{b} x^{
c} x^{d};
ca + ca b xb + ca bc xb xc + ca bcd xb xc xd
and we want to keep only the quadratic term. This can be done using
x^{a}::Weight(label=crd, value=1);
c^{#}::Weight(label=crd, value=0);
ca bc xb xc
Weights can be “inherited” by operators by using the WeightInherit property. Here is an
example using partial derivatives,
{\phi,\chi}::Weight(label=small, value=1);
\partial{#}::PartialDerivative;
\partial{#}::WeightInherit(label=all, type=multiplicative);
ex:=\phi \partial_{0}{\phi} + \partial_{0}{\lambda} + \lambda \partial_{3}{\chi
};
keep_weight(_, $small=1$);
76
Attached property Weight to [φ, χ ] .
Attached property PartialDerivative to ∂#.
Attached property WeightInherit to ∂#.
φ∂0 φ + ∂0 λ + λ∂3 χ
λ∂3 χ
If you want to use weights for dimension counting, in which operators can also carry a
dimension themselves (e.g. derivatives), then use the self attribute,
reset();
{\phi,\chi}::Weight(label=length, value=1);
x::Coordinate;
\partial{#}::PartialDerivative;
\partial{#}::WeightInherit(label=length, type=multiplicative, self=-1);
keep_weight(_, $length=1$);
φ∂x φ
6.9 Simplification
Collect factors in a product that differ only by their exponent. Note that factors containing
sub- or superscripted indices do not get collected (i.e. Am Am does not get reduced to
(Am )2 ).
ex:=A A B A B A;
77
AABABA
collect_factors(_);
A4 B 2
Arbitrary powers can be collected this way,
ex:=X X**(-1) X**(-4);
XX −1 X −4
collect_factors(_);
X −4
The exponent notation can be expanded again using expand_power.
ex:=X**4;
expand_power(_);
X4
XXXX
Collect terms in a sum that differ only by their numerical pre-factor. This is part of the
default post_process function, so does not need to be called by hand.
Note that this command only collects terms which are identical, it does not collect terms
which are different but mathematically equivalent. See sort_sum for an example.
Cadabra expressions are typically tensor expressions, which you cannot feed directly into
Sympy. With the map_sympy function you can recursively apply Sympy algorithms to the
scalar parts of Cadabra expressions.
The simplest example is when you have a scalar expression in Cadabra, for instance
ex:= \int{\sin(x)}{x};
78
Z
sin x dx
map_sympy(ex);
− cos x
The inert Cadabra expression gets evaluated by Sympy and then stored again in the ‘ex‘
object,
ex;
− cos x
In more complicated cases you may have a tensorial expression which you would like to
simplify using Sympy, for instance
ex:= (\sin(x)**2 + \cos(x)**2) A_{m} - A_{m};
(sin x )2 + (cos x )2 Am − Am
map_sympy(ex, "simplify");
6.9.4 simplify
simplify(_);
Am
By default it will use the Sympy backend, but if you have compiled Cadabra on a system
which has Mathematica installed, you can also switch it to use Mathematica instead, by
using
kernel(scalar_backend="mathematica")
79
6.10 Representations
6.10.1 decompose
The basis should be given in the second argument. All tensor symmetries, including those
implied by Young tableau Garnir symmetries, are taken into account. Example,
{m,n,p,q}::Indices(vector).
{m,n,p,q}::Integer(0..10).
R_{m n p q}::RiemannTensor.
Rmnqp Rmpnq
decompose(ex, $R_{m n p q} R_{m n p q}$);
1
−
2
Note that this algorithm does not yet take into account dimension-dependent identities,
but it is nevertheless already required that the index range is specified.
80
Attached property Integer to {m, n, p, q } .
However, in the present example, a Schouten identity makes the expression vanish identi-
cally in three dimensions,
{ m, n, p, q }::Integer(1..3);
ex:=A_{m n p} B_{m n q} - A_{m n q} B_{m n p};
decompose_product(ex)
canonicalise(ex);
6.10.3 lr tensor
Compute the tensor product of two tableaux or filled tableaux. The algorithm acts on
objects which have the Tableau or FilledTableau property, through which it is possible
to set the dimension. The standard Littlewoord-Richardson algorithm is used to construct
the tableaux in the tensor product. An example with Tableau objects is given below.
\tableau{#}::Tableau(dimension=10).
ex:=\tableau{2}{2} \tableau{2}{2}{1};
lr_tensor(_);
81
⊕ ⊕ ⊕ ⊕ ⊕
⊕ ⊕ ⊕
0 0 a a
⊗
1 1 b b
lr_tensor(_);
0 0 a 0 0 a 0 0
0 0 a a 0 0 a a
0 0 a a 1 1 b 1 1 1 1
⊕ 1 1 b ⊕ 1 1 ⊕ ⊕ ⊕
1 1 b b a a b a a
b b b
b b b b
1 ⊗ 2 ⊗ 3 ⊗ 4
converge(ex):
lr_tensor(_)
distribute(_)
;
1 2
1 2 3 1 2 4 1 2 1 3 4 1 3
1 2 3 4 ⊕ ⊕ ⊕ ⊕ 3 ⊕ ⊕ ⊕
4 3 3 4 2 2 4
4
82
1
1 3 1 4
2
2 ⊕ 2 ⊕
3
4 3
4
This algorithm is the partner of take_match; see the documentation of that algorithm for
further details.
The take_match and replace_match algorithms enable you to temporarily work only
on a part of an expression. You select the terms that you want to work on with
take_match. When you are done, put the result back into the larger expression with
replace_match.
A simple example shows how this works:
ex:=A C + B D G + C D A;
AC + BDG + CDA
take_match(_, $D Q??$);
BDG + CDA
substitute(_, $C -> Q$);
BDG + QDA
replace_match(_);
AC + BDG + QDA
As you can see here, the replacement C → Q was only done on the 2nd and 3rd term
of the original expression, to which we restricted all manipulations using the take_match
command.
83
This of course also works with more complicated, tensorial expressions, as the example
below shows.
ex:= A_{m n} \chi B^{m}_{p} + \psi A_{n p};
Amn χB m p + ψAnp
take_match(_, $\chi Q??$);
Amn χB m p
substitute(_, $A_{m n} -> C_{m n}$);
Cmn χB m p
replace_match(_);
Cmn χB m p + ψAnp
When you are working on a part of an expression, you can restrict attention further by
applying take_match again. The replace_match then puts sub-expressions back into the
larger expression in reverse order:
ex:=A B + C B + C D B;
AB + CB + CDB
take_match(_, $C Q??$);
CB + CDB
substitute(_, $C -> Q$);
QB + QDB
take_match(_, $D Q??$);
QDB
substitute(_, $B -> R$);
QDR
replace_match(_);
QB
replace_match(_);
AB + QB
84
6.11.3 zoom
Only show selected terms in a sum, and restrict subsequent algorithms to these terms.
Often you want manipulations to only apply to a selected subset of terms in a large sum.
The zoom algorithm makes only certain terms visible, representing the remaining terms
with dots.
Here is an expression with a 5 terms,
ex:=\int{ A_{m n} + B_{m n} C + D_{m} F_{n} C + T_{m n} + B_{m n} R}{x};
Z
(Amn + Bmn C + Dm Fn C + Tmn + Bmn R ) dx
In order to restrict attention only to the terms containing a Bmn factor, we use
zoom(_, $B_{m n} Q??$);
Z
(. . . + Bmn C + . . . + Bmn R ) dx
Subsequent algorithms only work on the visible terms above, not on the terms hidden
inside the dots,
substitute(_, $C->Q$);
Z
(. . . + Bmn Q + . . . + Bmn R ) dx
To make the hidden terms visible again, use unzoom, and note that the third term below
has remained unaffected by the substitution above,
unzoom(_);
Z
(Amn + Bmn Q + Dm Fn C + Tmn + Bmn R ) dx
85
86
Bibliography
6
[1] Kasper Peeters, Pierre Vanhove, and Anders Westerberg. “Supersymmetric higher-
derivative actions in ten and eleven dimensions, the associated superalgebras and
their formulation in superspace”. In: Class. Quant. Grav 18 (2001), pp. 843–889.
eprint: hep-th/0010167.
[2] Kasper Peeters and Anders Westerberg. “The Ramond-Ramond sector of string theory
beyond leading order”. In: Class. Quant. Grav. 21 (2004), pp. 1643–1666. eprint:
hep-th/0307298.
[3] Kasper Peeters. “Introducing Cadabra: a symbolic computer algebra system for field
theory problems”. In: (2007). eprint: hep-th/0701238.
[4] Kasper Peeters. “Cadabra2: computer algebra for field theory revisited”. In: J. Open
Source Softw. 3.32 (2018), p. 1118. DOI: 10.21105/joss.01118.
87