0

I have undestood that EPath is similar to xpath, but I am not sure. I want to match all the Sum in an expression. Consider

import sympy
x = sympy.IndexedBase('x', real=True)
i = sympy.Symbol('i', integer=True, positive=True, finite=True)
n = sympy.Symbol('n', integer=True, positive=True, finite=True)
expr = sympy.sin(sympy.Sum(2*x[i], (i, 1, n))) ** 2 + sympy.Sum(x[i], (i, 1, n))

I can match the Sum in the root note with

sympy.EPath(r"/Sum")

(actually it seems wrong to me, /*/Sum seems more correct). I can match the other one with

sympy.EPath(r"/*/*/Sum")

but I want all of them. From the documentation of xpath I read

// : Selects nodes in the document from the current node that match the selection no matter where they are

but it seems not supported by sympy:

sympy.EPath(r"//Sum")
---> ValueError: empty selector

If useful I have created a workaround looping on the expression tree:

def traverse_apply(expr, atype, func):
    """
    Apply the function `func` to all the element in the expression `expr` of type `atype`.
    Return a new expression.
    """
    if type(expr) == atype:
        
        return func(expr)
    if isinstance(expr, sympy.Basic):
        if not expr.is_Atom:
            args, basic = expr.args, True
        else:
            return expr
    elif hasattr(expr, '__iter__'):
        args, basic = expr, False
    else:
        return expr
    
    args = list(args)
    indices = range(len(args))
    
    for i in indices:

        arg = args[i]
        args[i] = traverse_apply(arg, atype, func)

    if basic:
        return expr.func(*args)
    else:
        return expr.__class__(args)
jared
  • 4,165
  • 1
  • 8
  • 31
Ruggero Turra
  • 16,929
  • 16
  • 85
  • 141
  • the documentation on w3schools is not correct. It should have been: `.//` : Selects nodes in the document from the current node that match the selection no matter where they are. See also https://stackoverflow.com/questions/35606708/what-is-the-difference-between-and-in-xpath – Siebe Jongebloed Jul 12 '23 at 09:23
  • Why you are using `Epath`? Is it really necessary for your application? Often, I find it easier to use the `find` method of symbolic expressions to look for a specific pattern. In you case, `expr.find(sympy.Sum)` returns a set containing all the `Sum` of your expression. – Davide_sd Jul 12 '23 at 09:37
  • @SiebeJongebloed, ok, but still doesn't work in sympy – Ruggero Turra Jul 12 '23 at 16:36
  • @Davide_sd, it seems `find` answers the question. The problem is that I want to use `EPath.apply` to modify part of the expression. How to do it with `find`? – Ruggero Turra Jul 12 '23 at 16:38

1 Answers1

0

For the purpose of this demonstration, I took the liberty to change the start value of the index i: it starts from zero. That's because I'm going to substitute the x variable of one summation with an array of numerical values. Remember, in Python indexing starts at 0.

import sympy
x = sympy.IndexedBase('x', real=True)
i = sympy.Symbol('i', integer=True, positive=True, finite=True)
n = sympy.Symbol('n', integer=True, positive=True, finite=True)
expr = sympy.sin(sympy.Sum(2*x[i], (i, 0, n))) ** 2 + sympy.Sum(x[i], (i, 0, n))
expr

Select all summations from expr with the find method:

matches = list(expr.find(Sum))
matches
# out: [Sum(x[i], (i, 0, n)), Sum(2*x[i], (i, 0, n))]

Also note that expr.find returns a set of elements. I converted them to a list in order to be able to index it. In Python, the conversion from a set to list is going to change order, so you might see a different ordering on your screen.

Now, I want to select Sum(x[i], (i, 0, n)), replace x with the array [0, 1, 2, 3, 4] (for simplicity) and n with the length of this array:

# must use sympy.Array, or sympy.Matrix, otherwise errors!
xvals = sympy.Array(list(range(5)))
# perform the substitution
new_val = matches[0].subs({x: xvals, n: len(xvals) - 1})
print(new_val)
# out: Sum([0, 1, 2, 3, 4][i], (i, 0, 4))

# evaluate the summation
new_val = new_val.doit()
new_val
# out: 10

Finally, replace the summation with new_val into the original expression:

new_expr = expr.subs(matches[0], new_val)
new_expr
# out: sin(Sum(2*x[i], (i, 0, n)))**2 + 10

The downside of this pattern/matching strategy is that it replaces every match of the summation specified on the first argument of subs, which might be undesirable. Other than that, it is quite easy and works well even for complicated expressions.

Davide_sd
  • 10,578
  • 3
  • 18
  • 30
  • the problem is that I would like to have a reference to the found sub-expression, not a copy – Ruggero Turra Jul 12 '23 at 21:44
  • 1
    SymPy is built with the concept of immutability. Whenever you edit an expression you get a new expression. Period. It is possible to hack your way and maybe get the desired result breaking immutability, but there are very high chances of introducing errors that are not apparent at first. – Davide_sd Jul 13 '23 at 06:31