Forcing multiplication to use __rmul__() instead of Numpy array __mul__() or bypassing the broadcasting

谁说我不能喝 提交于 2019-11-30 17:20:17

问题


This question is close to what is asked in Overriding other __rmul__ with your class's __mul__ but I am under the impression that this is a more general problem then only numerical data. Also that is not answered and I really don't want to use the matrix multiplication @ for this operation. Hence, the question.

I do have an object which accepts multiplication with scalars and numerical arrays. As usual, the left multiplication works fine since it is the myobj() methods are used but in the right multiplication, NumPy uses broadcasting rules and gives elementwise results with dtype=object.

This has also the side-effect of not being able to check the size of the array whether the size is compatible or not.

Therefore, the question is

Is there a way to force numpy array to look for the __rmul__() of the other object instead of broadcasting and performing elementwise __mul__()?

In my particular case, the object is a MIMO (multiple-input, multiple-output) transfer function matrix (or filter coefficients matrix if you will) so matrix multiplication has a special meaning in terms of adding and multiplying linear systems. Hence in each entry there is SISO system.

import numpy as np

class myobj():
    def __init__(self):
        pass

    def __mul__(self, other):
        if isinstance(other, type(np.array([0.]))):
            if other.size == 1:
                print('Scalar multiplication')
            else:
                print('Multiplication of arrays')

    def __rmul__(self, other):
        if isinstance(other, type(np.array([0.]))):
            if other.size == 1:
                print('Scalar multiplication')
            else:
                print('Multiplication of arrays')

A = myobj()
a = np.array([[[1+1j]]])  # some generic scalar
B = np.random.rand(3, 3)

With these definitions, the following commands show the undesired behavior.

In [123]: A*a
Scalar multiplication

In [124]: a*A
Out[124]: array([[[None]]], dtype=object)

In [125]: B*A
Out[125]: 
array([[None, None, None],
       [None, None, None],
       [None, None, None]], dtype=object)

In [126]: A*B
Multiplication of arrays

In [127]: 5 * A

In [128]: A.__rmul__(B)  # This is the desired behavior for B*A
Multiplication of arrays

回答1:


By default, NumPy assumes that unknown object (not inheriting from ndarray) are scalars, and it needs to "vectorize" multiplication over each element of any NumPy arrays.

To control the operations yourself, you need to set either __array_priority__ (most backwards compatible) or __array_ufunc__ (NumPy 1.13+ only). For example:

class myworkingobj(myobj):
    __array_priority__ = 1000

A = myworkingobj()
B = np.random.rand(3, 3)
B * A  # Multiplication of arrays



回答2:


I'll try to demonstrate what is going on.

In [494]: B=np.random.rand(3,3)

barebones class:

In [497]: class myobj():
     ...:     pass
     ...: 
In [498]: B*myobj()
...

TypeError: unsupported operand type(s) for *: 'float' and 'myobj'

add a __mul__

In [500]: class myobj():
     ...:     pass
     ...:     def __mul__(self,other):
     ...:         print('myobj mul')
     ...:         return 12.3
     ...: 
In [501]: B*myobj()
...
TypeError: unsupported operand type(s) for *: 'float' and 'myobj'
In [502]: myobj()*B
myobj mul
Out[502]: 12.3

add a rmul:

In [515]: class myobj():
     ...:     pass
     ...:     def __mul__(self,other):
     ...:         print('myobj mul',other)
     ...:         return 12.3
     ...:     def __rmul__(self,other):
     ...:         print('myobj rmul',other)
     ...:         return 4.32
     ...: 
In [516]: B*myobj()
myobj rmul 0.792751549595306
myobj rmul 0.5668783619454384
myobj rmul 0.2196204913660168
myobj rmul 0.5474970289273348
myobj rmul 0.2079367474424587
myobj rmul 0.5374571198848628
myobj rmul 0.35748803226628456
myobj rmul 0.41306113085906715
myobj rmul 0.499598995529441
Out[516]: 
array([[4.32, 4.32, 4.32],
       [4.32, 4.32, 4.32],
       [4.32, 4.32, 4.32]], dtype=object)

B*myobj() is given to B, as B.__mul__(myobj()), which proceeds to perform myobj().__rmul__(i) for each element of B.

In myobj()*B translates to myobj.__mul__(B):

In [517]: myobj()*B
myobj mul [[ 0.79275155  0.56687836  0.21962049]
 [ 0.54749703  0.20793675  0.53745712]
 [ 0.35748803  0.41306113  0.499599  ]]
Out[517]: 12.3

In [518]: myobj().__rmul__(B)
myobj rmul [[ 0.79275155  0.56687836  0.21962049]
 [ 0.54749703  0.20793675  0.53745712]
 [ 0.35748803  0.41306113  0.499599  ]]
Out[518]: 4.32

You can't do anything in myobj to override the translation of B*myobj() to B.__mul__(myobj()). Use functions or methods if you need greater control over operation. It's hard to fight the interpreter.



来源:https://stackoverflow.com/questions/40694380/forcing-multiplication-to-use-rmul-instead-of-numpy-array-mul-or-byp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!