On Jul 13, 7:25 am, Aaron Meurer <asmeu...@gmail.com> wrote: > One thing that I have noticed with regard to overriding __mul__ and > __rmul__ (for example) is that you can never completely control what > happens to your class because of association. For example, suppose > that A and B are MatrixExprs and x is some Expr object (say, a > Symbol). Suppose that A*B should give a ShapeError, and that x is a > scalar, which does not matter where it is in the expression. Then you > have to program it so that all of the following give ShapeError: > > 1. x*(A*B) > 2. (x*A)*B > 3. (A*x)*B > 4. A*(x*B) > 5. (A*B)*x > 6. A*(B*x) > > Let us look at these. Obviously, 1 and 5 will work, since you have > complete control over A.__mul__(B). Similarly, in 4 and 6, you will > end up with A.__mul__(Mul(x, B)), but this is not a problem, since you > can make A.__mul__ check for the left-most noncommutative in a Mul. > > But what about 2 and 3? Here, we have __mul__ being called by a Expr > type, namely, Mul(x, A). You want to get B.__rmul__(Mul(x, A)), since > you have implemented shape checking logic in MatrixExpr.__(r)mul__. > But b.__rmul__(a) is called with a*b only if a.__mul__(b) returns > NotImplemented. So, basically, we need Mul.__mul__(MatrixExpr) to > return NotImplemented. Well, let us look at the code for Mul.__mul__ > (actually Expr.__mul__): > > @_sympifyit('other', NotImplemented) > @call_highest_priority('__rmul__') > def __mul__(self, other): > return Mul(self, other) > > Well, this is not very helpful, because the logic is buried in some > decorators. So let us look at the code for those decorators. I will > not bore you by pasting the entire code here (you can see it in > sympy/core/decorators.py), but basically, _sympifyit makes tries to > sympify other, and makes __mul__ return NotImplemented if it fails. I > am not delving into this, because sympify would not fail for > MatrixExpr (otherwise, we would actually have a problem doing any form > of x*A*B). > > So call_highest_priority seems to be a better bet. This decorator I > will also not paste here, but it basically lets you define > ._op_priority on your object, and if it is greater than Expr's (which > is quite arbitrarily set to 10.0), then it will call > other.__rmul__(self). > > But wait, there's more. _op_priority can make the above cases work, > but * is not the only way that SymPy multiplies things. You will also > have to deal with variations on Mul(x, A, B). Mul completely ignores > _op_priority. Unfortunately, even if this may seem like a more > esoteric way to multiply things that you can just recommend users > avoid, it is used internally a lot, because Mul(*args) is more > efficient than reduce(operator.mul, args). > > Thus, you see that it is quite impossible to make things work 100% of > the time without modifying the core. And actually, because of the Mul > thing that would not work at all and that is called by so many core > functions and methods, you will not even get something like things > working 90% of the time, but instead things will break when used a > certain way, and you will have hard to track bugs.
So, if I understood correctly, we would just have to make Mul() raise a NotImplementedError if it encounters user-defined types? Vinzent -- You received this message because you are subscribed to the Google Groups "sympy" group. To post to this group, send email to sympy@googlegroups.com. To unsubscribe from this group, send email to sympy+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sympy?hl=en.