Cadabra
a field-theory motivated approach to computer algebra

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.

Fundamental Cadabra objects: Ex and ExNode

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);
\(\displaystyle{}A+B\)
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. 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());
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 somewhat 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)
\(\displaystyle{}A_{m n}\)
A_{m n}
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.

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};
\(\displaystyle{}A+B+C_{m} D^{m}\)
A + B + C_{m} D^{m}
for n in ex: print(str(n))
A + B + C_{m} D^{m}
A
B
C_{m} D^{m}
C_{m}
m
D^{m}
m
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))
\sum
A
B
\prod
C
m
D
m
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}
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)
A
B
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.

Arguments and indices

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} };
\(\displaystyle{}\text{Attached property Derivative to }\nabla{\#}.\)
\(\displaystyle{}\nabla_{m}\left(A^{n}\,_{p} V^{p}\right)\)
\nabla_{m}(A^{n}_{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))
A^{n}_{p} V^{p}
m
n

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 += 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:= \nabla_{a}{ h^{b}_{c} }; expand_nabla(ex);
\(\displaystyle{}\text{Attached property Derivative to }\nabla{\#}.\)
\(\displaystyle{}\nabla_{a}{h^{b}\,_{c}}\)
\nabla_{a}(h^{b}_{c})
\(\displaystyle{}\partial_{a}\left(h^{b}\,_{c}\right)+\Gamma^{b}\,_{a p} h^{p}\,_{c}-\Gamma^{p}\,_{a c} h^{b}\,_{p}\)
\partial_{a}(h^{b}_{c}) + \Gamma^{b}_{a p} h^{p}_{c}-\Gamma^{p}_{a c} h^{b}_{p}
ex:= \nabla_{a}{ v_{b} w^{b} }; expand_nabla(ex);
\(\displaystyle{}\nabla_{a}\left(v_{b} w^{b}\right)\)
\nabla_{a}(v_{b} w^{b})
\(\displaystyle{}\partial_{a}\left(v_{b} w^{b}\right)\)
\partial_{a}(v_{b} w^{b})
ex:= \nabla_{\hat{a}}{ h_{b c} v^{c} }; expand_nabla(ex);
\(\displaystyle{}\nabla_{\widehat{a}}\left(h_{b c} v^{c}\right)\)
\nabla_{\hat{a}}(h_{b c} v^{c})
\(\displaystyle{}\partial_{\widehat{a}}\left(h_{b c} v^{c}\right)-\Gamma^{p}\,_{\widehat{a} b} h_{p c} v^{c}\)
\partial_{\hat{a}}(h_{b c} v^{c})-\Gamma^{p}_{\hat{a} b} h_{p c} v^{c}
Copyright © 2001-2018 Kasper Peeters
Questions? info@cadabra.science