# Expansion of covariant derivative

At the end of the page Programming in Cadabra, there is a function expand_nabla, which expands the covariant derivative in terms of partial derivative and contractions with the connection.

In that implementation the dummy index contracting the connection with the tensor is called p, i.e. is fixed. Since the dummy index is fixed, consecutive application of the covariant derivative cannot be expanded. For example, if we consider the code below,

foo := \nabla_{a}{ \nabla_{b}{ h^{c}_{d} } };
expand_nabla(_);

an error is raised since the expansion of the inner covariant derivative introduces the index p, and then the expansion of the second covariant derivative attempts to introduce another p index (already in use).

## Question

How could the definition of the expand_nabla function be generalised to modify automatically the name of the dummy index?

Moreover, assuming that we have a set of symbols with the property Indices assigned, Is it possible to preserve the type of index? i.e. to introduce a new dummy index using a symbol of the list.

+1 vote

I faced the same problem in my time, so I ended up writing a function like this.

def select_index(used_indices):
indeces = r'w v u s r q o n m l k j i h g f e d c b a'.split()
for uind in indeces:
found = False
for qind in used_indices:
if qind == uind:
found = True
break
index = uind
break
return Ex(index), used_indices

def one_nabla(ex, used_indices):
t3, used_indices = select_index(used_indices)
free = dict()
free['sub'] = set()
free['up'] = set()
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():
if index.parent_rel==sub:
else:
for key in free.keys():
for index in free[key]:
ind = Ex(index)
if key == 'sub':
t1:= -\Gamma^{@[t3]}_{@(dindex) @[ind]};
else:
t1:=  \Gamma^{@[ind]}_{@(dindex) @[t3]};
t2:= @[arg];
for term_index in arg.free_indices():
if str(term_index.ex()) == index:
if term_index.parent_rel==sub:
t2[term_index]:= _{@[t3]};
else:
t2[term_index]:= ^{@[t3]};
ret += Ex(str(nabla.multiplier)) * t1 * t2
nabla += ret #
break
break
return ex, used_indices

def nabla_calculation(ex, used_indices, count):
ex = ex.ex()
for element in ex.top().terms():
new:=1;
local_count = 0
for nabla in element[r'\nabla']:
local_count += 1
if local_count == 0:
continue
elif local_count == 1:
new, used_indices = (one_nabla(element, used_indices))
element.replace(new.ex())
count -= 1
else:
if element.ex().top().name == r'\prod':
i = 0
while i < 2:
for mult in element.ex().top().children():
local_count2 = 0
for nabla in mult[r'\nabla']:
local_count2 += 1
if local_count2 == 0:
new *= mult.ex()
elif local_count2 == 1:
for nabla in mult[r'\nabla']:
nabla1, used_indices = one_nabla(nabla, used_indices)
nabla.replace(nabla1.ex())
new *= nabla.ex()
else:
mult1, used_indices = nabla_calculation(mult, used_indices, local_count)
new *= mult1
i+=1
new *= Ex(str(element.multiplier))
element.replace(new)
else:
for nabla in element[r'\nabla']:
for arg1 in nabla.args():
arg2, used_indices = nabla_calculation(arg1, used_indices, count - 1)
index = nabla.indices().__next__()
t := \nabla_{@(index)}{@[arg2]};
new = t
nabla1, used_indices = one_nabla(new, used_indices)
new = Ex(str(nabla.multiplier)) * nabla1
nabla.replace(new)
return ex, used_indices

def expand_nabla(ex):
if ex.top().name == '\equals':
for child in ex.top().children():
ret:=0;
for element in child.ex().top().terms():
count = 0
used_indices = set()
for nabla in element.ex()[r'\nabla']:
count += 1
if count == 0:
ret += element.ex()
#new_ex += element.ex()
else:
for n in element.ex():
for index in n.indices():
#for nabla in element[r'\nabla']:
element1, used_indices = nabla_calculation(element, used_indices, count)
ret +=element1
child.replace(ret)
else:
for element in ex.top().terms():
count = 0
used_indices = set()
for nabla in element.ex()[r'\nabla']:
count += 1
if count == 0:
pass
else:
for n in element.ex():
for index in n.indices():
element1, used_indices = nabla_calculation(element, used_indices, count)
element.replace(element1)
return ex

To use it, you need to call the main function expand_nabla(ex). It is suitable for large expressions, does not duplicate already occupied indexes, and also knows how to work with equalities.

by (780 points)

This routine throws a RuntimeError: Free indices in different terms in a sum do not match.

Thank you for the comment @Arina. Sorry for my (super) later reply.

+1 vote

There's a very simple workaround for this.

{\mu, \nu, \lambda, \kappa, \rho, \sigma#}::Indices(space, position=independent).

\partial{#}::PartialDerivative.
\nabla{#}::Derivative.

A{#}::Depends(\partial{#}, \nabla{#}).

ex1:= A_\mu.
ex2:= B_{\mu \nu} = \nabla_{\mu}{ A_\nu }.

expand_nabla(ex2)
rename_dummies(ex2);

ex3:= C_{\mu \nu \rho} = \nabla_{\mu}{B_{\nu \rho }}.
substitute(ex3, ex2)

expand_nabla(ex3);

You essentially have to rename dummy indices after each use of expand_nabla.

by (610 points)

Thank you for the comment @jsem.

This only works if you have one nabla in the summand. If there is more than one, they all get the same dummy index.

What do you mean? If you need more nablas, you can act with one, rename dummies, act again with the next nabla, etc. It's not an elegant solution, hence only a workaround.

I mean that if you have something like \nabla_{\rho}(\nabla_{\nu}(\xi_{\mu})),

this function from Programming in Cadabra expands it with two identical dummies.

That is true. That was the original question. My code produces a workaround that seems to do the job.

Sorry, I was inattentive, that's what you offer the solution for. Unfortunately, if we are talking about expressions with ~100 summands, this approach is very problematic((

Indeed. It's not a real solution, just a workaround for simple cases.

Your code didn't seem to run at all for me, so I don't know of a better solution unfortunately.

Sorry for that, you can email me, maybe we can figure out what the problem is. arshtm@gmail.com