Welcome to Cadabra Q&A, where you can ask questions and receive answers from other members of the community.
+1 vote

I'm curious if other people have good strategies for avoiding index clashes when handling ExNodes directly.

I have in mind the example of the covariant derivative in https://cadabra.science/notebooks/ref_programming.html, which works fine so long as the index p on the connection doesn't appear in the original expression. (If it does, it breaks.) As far as I can tell, introducing indices when hacking ExNodes directly can easily lead to this.

Presumably the only solution is to work hard to avoid index clashes? This is what substitute does behind the scenes when it's running, I gather.

in General questions by (1.0k points)

2 Answers

+1 vote

Well, substitute mostly relies on several helper functions in IndexHelper.hh|cc to figure out whether dummy indices clash with free indices and in order to get a new dummy index which does not clash with anything already present. We could export some of that functionality to the Python side (a particularly useful one would be get_dummy with just two arguments: the index type and the position in the tree).

by (82.6k points)

One thing I was trying to tinker with just now is to see if I could figure out how to just call substitute on an ExNode directly on the Python side. Because all of this functionality is already on the C++ side in substitute::apply (which takes an iterator, close enough to an ExNode), and it seems silly to reinvent the wheel.

But from an elegance perspective, it kind of messes with how nicely all the algorithms behave uniformly if can be called on an ExNode too.

(This isn't really a feature request. I'm just tinkering on a local branch to try to understand your code. I wish I had looked under the hood years ago!)

Actually, Indices does export get_dummy, Dom beat me to it.

Making it possible to call algorithms directly on an ExNode makes a lot of sense. Historically Ex came first, then the python binding, and then ExNode. But any C++ algorithm relies only on can_apply and apply, which both take an iterator, not an Ex itself (algoritnms still need to know in which Ex the iterator lives, but that's not really a problem).

Yeah, I was just looking at that, and it looks like you just call ex.begin(). So one could just add an optional ExNode argument and then use that as the iterator.

Does depth counter track from the beginning of the iterator, or does it compute from its location in the tree?

+1 vote

Here is a solution that solves some of these issues:

{a,b,c,d,a#}::Indices(vector).
{i,j,k,l,i#}::Indices(isospin).
\nabla{#}::Derivative.
\partial{#}::PartialDerivative.

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():
                indexprop = Indices.get(index, ignore_parent_rel = True)
                if indexprop.set_name == 'vector':
                    t2:= @(arg);
                    dummy = indexprop.get_dummy(ex)
                    if index.parent_rel==sub:
                        t1:= -\Gamma^{@(dummy)}_{@(dindex) @(index)};
                        t2[index]:= _{@(dummy)};
                    else:
                        t1:=  \Gamma^{@(index)}_{@(dindex) @(dummy)};
                        t2[index]:= ^{@(dummy)};
                    ret += Ex(str(nabla.multiplier)) * t1 * t2
            nabla += ret
    return ex

This version grabs a dummy index by looking at the entire expression tree, while in practice, we might just want to look at the term in the top-level sum. It knows to ignore isospin indices and only grab vector indices. We also need \nabla and \partial to have IndexInherit (inherited from Derivative).

by (1.0k points)
...