Question

Multiplication distributed over addition in sympy does not seem to evaluate the multiplications.

I've made a subclass of sympy.Symbol that knows how to multiply itself by other things. As a minimal working example, let's just pretend the subclass just eats up anything that's multiplied by it:

from sympy import *

class Gobbler(Symbol):
    _op_priority = 1.0e200
    def __mul__(self, other):
        return Gobbler('gob('+self.name+'*'+str(other)+')')
    def __rmul__(self, other):
        return Gobbler('gob('+self.name+'*'+str(other)+')')

x = Gobbler('x')
y = Gobbler('y')
a = Symbol('a')
b = Symbol('b')

[Yes, that _op_priority is ridiculous. But changing it to more respectable numbers ~10.0 doesn't change anything.] I can run

>>> x*a
gob(x*a)
>>> x*a + y*a
gob(x*a) + gob(y*a)

Everything works quite naturally until I get to

>>> expand((x+y)*a)
x*a + y*a

Why don't these get gobbled?! The result looks exactly like what I entered at the previous prompt, but nothing happened.

The Gobblers are now factors in Muls, two of which are terms in an Add. So how can I make those Muls evaluate? I've tried every combination of simplify, expand, etc., with every combination of options I can think of. But nothing makes these go anywhere. I can even extract the first half of the sum with .args[0] and try to simplify/expand that. Nothing!

Even worse, my actual use case involves lots of nested expressions like

>>> b*expand((x+y)*a)
b*(x*a + y*a)

What is going on? How can I make it work? What's the magic word?

Was it helpful?

Solution 2

Well, it's not what I had hoped for, but I think the solution will necessarily involve going through the expression and forcing the applications. Applying such applications is like applying simplify, so it's not too unexpected or onerous. The following is sufficient for my purposes, though more general expressions might require more special cases. The essential ideas are found here, in the tutorial, and involve recursing through the expression tree:

def gobbleExpr(expr):
    if isinstance(expr, Gobbler):
        return expr
    if isinstance(expr, Mul):
        args = list(o if o.is_Atom or isinstance(o, Gobbler)
                    else gobbleExpr(o)
                    for o in expr.args)
        gobbler = prod(t for t in args if isinstance(t, Gobbler))
        others = prod(o for o in args if not isinstance(o, Gobbler))
        if gobbler==1:
            return others
        else:
            return gobbler.__mul__(others)
    if isinstance(expr, Add):
        return sum(gobbleExpr(arg) for arg in expr.args)
    return expr

So now I can do something like

>>> expand(b*(x+y)*a)
x*a*b + y*a*b
>>> gobbleExpr(_)
gob(gob(x*1)*a*b) + gob(gob(y*1)*a*b)

So the only remaining things are Gobblers added to each other, which I handle elsewhere.

OTHER TIPS

Mul doesn't call __mul__ (__mul__ is only called when you use a *, as it's Python's operator overloading). SymPy currently doesn't provide a way to dispatch on Mul, though it's on our TODO list. The main thing holding it back is that we aren't really sure how to do it. Your solution is probably the best way (except with the correction I mentioned).

You can also play with setting _op_priority (search the SymPy code-base to see how to use this).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top