On Fri, Jul 23, 2010 at 5:44 PM, Brian Granger <elliso...@gmail.com> wrote:
> On Wed, Jul 21, 2010 at 2:11 PM, Ondrej Certik <ond...@certik.cz> wrote:
>> Hi,
>>
>> On Wed, Jul 21, 2010 at 1:15 PM, Aaron S. Meurer <asmeu...@gmail.com> wrote:
>>> First off, pragmatically speaking, is there a reason why you need to use 
>>> the core Mul instead of writing your own custom Mul for the quantum stuff?  
>>> I am just saying this because even though I agree that we need this change, 
>>> changing anything in the core is very difficult and time consuming, because 
>>> the code is so fragile, and you also have to pay close attention to speed 
>>> concerns.
>>>
>>> Also, Mul is optimized for commutative arguments, with non-commutatives 
>>> special cased (read "hacked in").  I am thinking it might be smarter to 
>>> have separate NCMul for general non-commutatives anyway.
>>>
>>> So basically, these suggestions are on how to fix issue 1941, because as I 
>>> see it, validating whether two objects can be multiplied together is just a 
>>> special case of objects that combine with each other specially.
>>>
>>> On Jul 21, 2010, at 1:34 PM, Brian Granger wrote:
>>>
>>>> Hi,
>>>>
>>>> We are running into some issues with Mul in the quantum stuff.  There
>>>> are two main things:
>>>>
>>>> 1.  How Mul combines adjacent args.
>>>
>>> This one may prove to be the more difficult of the two to solve, because of 
>>> the way Python evaluates expressions like a*b*c.  Also, you have to 
>>> consider that no matter how you try to make things work with __mul__ and 
>>> __rmul__, it can always be thrown off with something like a*(b*c).  Option 
>>> 1 alone might not work for this reason.
>>>
>>>> 2.  How Mul decides if two adjacent args can be multiplied.
>>>>
>>>> For now, let's focus on the second of these.  The difficulty right now
>>>> is that Mul allow any two Exprs to be multiplied.  But in quantum
>>>> mechanics, certain things can't be multiplied.  For example, the
>>>> following don't make sense:
>>>>
>>>> Ket('a')*Operator('O)
>>>> Operator('O')*Bra('a')
>>>>
>>>> We would like to add logic to check for these cases into the
>>>> __mul__/__rmul__ methods of Operator and State.  But, this logic will
>>>> never be triggered in situations like this:
>>>>
>>>> Bra('a')*Ket('a')*Operator('O) = Mul(Bra('a'), Ket('a'))*Operator('O')
>>>>
>>>> Because the Mul*Operator triggers Mul.__mul__ (which has no validation
>>>> for Operators or States) rather than Operator.__rmul__.
>>>>
>>>> I see two ways out of this:
>>>>
>>>> 1.  Handle this like numpy arrays that have an __array_priority__
>>>> attribute.  The idea is that the array having the highest numeric
>>>> value of the __array_priority__ attribute would have its __mul__ or
>>>> __rmul__ method called.  This would make it possible for:
>>>>
>>>> Mul(Bra('a'), Ket('a'))*Operator('O') to trigger Operator.__rmul__
>>>>
>>>> This solution would not required changes to Mul, only to the __mul__
>>>> and __rmul__ methods of Expr.  It would also require going through
>>>> sympy and adding the priority atttributes to relevant classes.
>>>>
>>>> 2.  To add a new set of methods to Expr that are used by Mul to
>>>> validate if two things could be multiplied.  This is more flexible.  I
>>>> propose methods like:
>>>>
>>>> _validate_mul(self, other)
>>>> _validate_rmul(self, other)
>>>>
>>>
>>> It seems to me like you would have to have both options.  The _validate_mul 
>>> (or combine_mul, as Ondrej called it in 
>>> http://github.com/certik/sympyx/commits/handler) would have to be there to 
>>> handle the special multiplication behaviors.  But I think you also need 
>>> some kind of priority system to break ties of more than one object in the 
>>> Mul has such a method.  If you just do validation (no combining), then 
>>> maybe you could do without option 1, except you will have to accept the 
>>> fact that _validate_mul will always be called before _validate_rmul.
>>>
>>> By the way, it is more general to send to the combination method the whole 
>>> list of objects that have been gathered together already.  This is for 
>>> things that combine into each other, such as infinities and Order, so they 
>>> can pull everything into themselves.
>>>
>>>> The end of Mul.flatten would check for these attributes and then
>>>> validate adjacent objects to make sure they could really be
>>>> multiplied.  This would help us with symbolic Matrices and Tensors for
>>>> example, because these methods could do shape compatibility tests.
>>>>
>>>> This issue is also related to how combines are done in Mul.flatten,
>>>> but I think we might be able to resolve this by itself.  Thoughts?
>>>
>>> Maybe for now you could do validation separate from combination, if it's 
>>> easier to handle.  Ultimately, I would like to see both separated out into 
>>> the objects. The validation is a trivial special case of the combination, 
>>> but not the other way around, because combination requires handling all 
>>> args, not just adjacent ones.  On the other hand, the issue #1 you name 
>>> above could cause efficiency problems for the combination, so might need to 
>>> be solved concurrently.
>>
>>
>> Aaron, you essentially summarized it all, thanks!
>>
>> Some points:
>>
>> 1) Problem with extending the Mul is that if it's not done right, it
>> slows things down a lot.
>
> Definitely.  I may play with this some.
>
>> 2) a*b and Mul(a, b) should be equivalent
>
> I don't see how this can be the case, but maybe I am misunderstanding
> you.  Are you saying that the __mul__ and __rmul__ methods must always
> return Mul and never custom subclasses?  Different mathematical
> entities have different multiplication rules and this goes beyond mere
> commutativity.  A perfect example is Matric/vector/tensor
> multiplication.  If we ever want to have these classes inhert from
> Basic and be used in symbolic contexts, we really need a custom Mul
> that is created by A*B.

Yes. But my point is that Mul is supposed to handle all of this. E.g.
"*" is "Mul".  So a*b and Mul(a, b) are equivalent. And it's the job
of Mul to do the right thing with the objects.

It's probably not the right approach. So we'll have Mul, that handles
basic multiplication in the core. Then QuantumMul, that does something
else, and NCMul, that handles noncommutative multiplication.

Now all these statements are different:

Mul(a, b)
NCMul(a, b)
QuantumMul(a, b)

now the question is what happens with

a*b

and that is solved by your priorities idea. So I am +1 to that.

Ondrej

-- 
You received this message because you are subscribed to the Google Groups 
"sympy" group.
To post to this group, send email to sy...@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