Home > Blockchain >  numpy: multiply arrays without broadcasting
numpy: multiply arrays without broadcasting

Time:07-25

I don't know if the title of the question makes sense, but I couldn't think of a better title.

Please consider the following scenario: I have two numpy arrays a and b, b has shape (2, 2, 5) and a has shape (5,). I would like to multiply each element of a by each 2x2 ARRAY in b, I DON'T want to multiply each element in a by each ELEMENT in each 2x2 array in b, which is what happens if I simply do a * b

The following code demonstrates my problem and my desired result:

import numpy as np

np.random.seed(1234)


class MyClass:
    def __mul__(self, other):
        print(f'{type(self).__name__} * {other}')
        return other

    def __rmul__(self, other):
        print(f'{other} * {type(self).__name__}')
        return other


a = np.full((5,), MyClass())
b = np.random.uniform(-1, 1, (2, 2, 5))

a * b
# MyClass * -0.6169610992422154
# MyClass * 0.24421754207966373
# ...
# MyClass * 0.5456532432247481
# MyClass * 0.7652823812722331

# Desired result:
[ai * bi for ai, bi in zip(a, np.moveaxis(b, -1, 0))]
# MyClass * [[-0.6169611  -0.45481479]
#  [-0.28436546  0.12239237]]
# ...
# MyClass * [[ 0.55995162  0.75186527]
#  [-0.25949849  0.76528238]]

# EDIT: Solution suggested by "Guimoute", does the same as a * b but also introduces addition (not a solution).
b @ a
# -0.6169610992422154 * MyClass
# 0.24421754207966373 * MyClass
# ...
# 0.5456532432247481 * MyClass
# 0.7652823812722331 * MyClass

c = np.split(np.moveaxis(b, -1, 0).reshape(-1, 2), 5)
# c is now a list of numpy arrays, with length = 5

a * c
# Raises an exception
# ValueError: operands could not be broadcast together with shapes (5,) (5,2,2)

EDIT: I should probably clarify that what I'm asking for is a numpy solution, rather than one involving a python loop ie: [ai * bi for ai, bi in zip(a, np.moveaxis(b, -1, 0))].

EDIT: I added the suggestion by "Guimoute", involving matmul @ operator, which as can be seen is clearly not a solution.

EDIT: To address complaints that it was hard to verify potential solutions without having to check the order of some printed outputs I have added the following example which includes a function to check whether what a function does is a solution:

import numpy as np

np.random.seed(1234)


class MyClass:
    def __mul__(self, other):
        return self

    def __rmul__(self, other):
        return self


def solution_involving_python_loop(a, b):
    return np.array([ai * bi for ai, bi in zip(a, np.moveaxis(b, -1, 0))])

def is_valid_solution(func):
    return func(a, b).ndim == 1


a = np.full((5,), MyClass())
b = np.random.uniform(-1, 1, (2, 2, 5))


print(is_valid_solution(solution_involving_python_loop))
# True

CodePudding user response:

import numpy as np

np.random.seed(1234)


class MyClass:
    def __mul__(self, other):
        print(f'{type(self).__name__} * {other}')
        return other

    def __rmul__(self, other):
        print(f'{other} * {type(self).__name__}')
        return other


a = np.full((5,), MyClass())
b = np.random.uniform(-1, 1, (2, 2, 5))

your_sol = np.array([ai * bi for ai, bi in zip(a, np.moveaxis(b, -1, 0))])

your_required_array = np.moveaxis(a*b, -1, 0).astype(float)

CodePudding user response:

Solution: Split b into an array (with dtype=object) containing all the 2x2 arrays

c = np.empty((5,), dtype=object)
c[:] = np.split(np.moveaxis(b, -1, 0).reshape(-1, 2), 5)
# c is now an object array of numpy arrays, with length = 5

a * c
# MyClass * [[-0.6169611  -0.45481479]
#  [-0.28436546  0.12239237]]
# ...
# MyClass * [[ 0.55995162  0.75186527]
#  [-0.25949849  0.76528238]]

Not a solution that I like, but nobody seems to have a better one.

CodePudding user response:

You want to use the matrix multiplication operator @:


import numpy as np

np.random.seed(1234)

a = np.random.randn(5)
b = np.random.uniform(-1, 1, (2, 2, 5))
print(b@a)

# [[0.35278726 0.16635596]
#  [1.89830443 0.25534444]]
  • Related