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.

Reply via email to