# 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 inex:=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

`ExNode`

s when you do a standard Python iteration
over the elements of an `Ex`

, as infor 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 += 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);

\(\displaystyle{}\text{Attached property Derivative to }\nabla{\#}.\)

\(\displaystyle{}\frac{1}{2}\nabla_{a}{h^{b}\,_{c}}\)

1/2 \nabla_{a}(h^{b}_{c})

\(\displaystyle{}\frac{1}{2}\partial_{a}\left(h^{b}\,_{c}\right)+\frac{1}{2}\Gamma^{b}\,_{a p} h^{p}\,_{c} - \frac{1}{2}\Gamma^{p}\,_{a c} h^{b}\,_{p}\)

1/2 \partial_{a}(h^{b}_{c}) + 1/2 \Gamma^{b}_{a p} h^{p}_{c} - 1/2 \Gamma^{p}_{a c} h^{b}_{p}

ex:= 1/4 \nabla_{a}{ v_{b} w^{b} };
expand_nabla(ex);

\(\displaystyle{}\frac{1}{4}\nabla_{a}\left(v_{b} w^{b}\right)\)

1/4 \nabla_{a}(v_{b} w^{b})

\(\displaystyle{}\frac{1}{4}\partial_{a}\left(v_{b} w^{b}\right)\)

1/4 \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}