Python invert element order for non-commutative in-place operator

43 Views Asked by At

Python offers in-place operators (+=, *=, /=, @= etc.) to improve memory efficieny when altering a variable. However, the variable you are modifying is always on the 'left' side of the operator: a /= b amounts to a = a*b, not a = b*a. In this example, this does not matter, as multiplication is commutative. However, for non-commutative operations like subtraction, division and (for me specifically) matrix multiplication, how can one invert this order? Something like:

a -= b # -> a = a-b
a =- b # -> a = b-a

In most cases I would not bother using inline operations, but I am simulating rigid bodies and have to perform a lot of matrix operations as efficiently as possible.

1

There are 1 best solutions below

0
Axel Kemper On

To evaluate the effects and options, you could use something like the following:

# https://medium.com/@noransaber685/demystifying-python-bytecode-a-guide-to-understanding-and-analyzing-code-execution-6a163cb83bd1
import dis

# https://www.pythoninformer.com/python-language/magic-methods/in-place-operator-overload/
class Matrix:

    def __init__(self, a, b, c, d):
        self.data = [a, b, c, d]

    def __str__(self):
        return '[{}, {}][{}, {}]'.format(self.data[0],
                                         self.data[1],
                                         self.data[2],
                                         self.data[3])

    def __add__(self, other):
        if isinstance(other, Matrix):
            return Matrix(self.data[0] + other.data[0],
                          self.data[1] + other.data[1],
                          self.data[2] + other.data[2],
                          self.data[3] + other.data[3])
        else:
            return NotImplemented
        
    def __iadd__(self, other):
        if isinstance(other, Matrix):
            self.data[0] += other.data[0]
            self.data[1] += other.data[1]
            self.data[2] += other.data[2]
            self.data[3] += other.data[3]
            return self
        else:
            return NotImplemented

ma = Matrix(1, 2, 3, 4)
mb = Matrix(5, 6, 7, 8)

def f1():
    global ma
    ma += mb
    
def f2():
    global ma
    ma = ma + mb
    
def f3():
    global ma
    ma = mb + ma


print("f1:")
dis.dis(f1)

print("f2:")
dis.dis(f2)

print("f3:")
dis.dis(f3)

Output:

f1:
 40           0 LOAD_GLOBAL              0 (ma)
              2 LOAD_GLOBAL              1 (mb)
              4 INPLACE_ADD
              6 STORE_GLOBAL             0 (ma)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
f2:
 44           0 LOAD_GLOBAL              0 (ma)
              2 LOAD_GLOBAL              1 (mb)
              4 BINARY_ADD
              6 STORE_GLOBAL             0 (ma)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
f3:
 48           0 LOAD_GLOBAL              0 (mb)
              2 LOAD_GLOBAL              1 (ma)
              4 BINARY_ADD
              6 STORE_GLOBAL             1 (ma)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE

The main differences are store/load operations of (temporary) variables. In terms of runtime, these should take less time than the actual computations.