Numpy multiplies array of vectors as if its a matrix

Question:

I am making a 3d renderer in python and an extremely common operation is multiplying an array of vectors by a matrix. I knew about numpys @ operator and board casting so I just did matrix @ arrayOfVectors which seemed to be fine, but it was giving the wrong output. I realized that numpy was treating array of vector as a matrix (4×8 in this case), and it was multiplying them accordingly. I have switched to using a for loop, which does have some advantages, but I understand that this is much slower, and should be avoided if possible. I would like to find a way to apply a matrix to each vector in an array of vectors, and in some cases also divide each vector by its w component afterwards. Is there a way of doing this as neat is matrix @ arrayOfVectors or must I resort to for vector in arrayOfVectors: matrix @ array.

Asked By: Jacob

||

Answers:

In [14]: A = np.arange(12).reshape(3,4); V = np.arange(8).reshape(2,4)

The first dimensions are different, so direct matmul not only doesn’t give the desired answer, it gives an error. It is trying to do matrix multiplication for 2 arrays:

In [15]: A@V
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [15], in <cell line: 1>()
----> 1 A@V

ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 2 is different from 4)

What you want is to treat the first dimension of V as a "batch":

In [17]: [A@v for v in V]
Out[17]: [array([14, 38, 62]), array([ 38, 126, 214])]

We get that by making V as 3d array – read matmul docs:

In [18]: A@V[:,:,None]
Out[18]: 
array([[[ 14],
        [ 38],
        [ 62]],

       [[ 38],
        [126],
        [214]]])

In [19]: _.shape
Out[19]: (2, 3, 1)

We can remove the trailing size 1 dimension if necessary (squeeze)

This kind of dimension specification is easy with einsum:

In [20]: np.einsum('ij,kj->ik',A,V)
Out[20]: 
array([[ 14,  38],
       [ 38, 126],
       [ 62, 214]])

I could have used ‘ki’ to get a (2,3) answer.

Answered By: hpaulj