(original) (raw)

In Sun, Jun 10, 2018 at 4:31 PM Eric Wieser <wieser.eric+numpy@gmail.com> wrote:

Thanks for the writeup Marten,

Indeed, thank you Marten!

This hits on an interesting alternative to frozen dimensions - np.cross could just become a regular ufunc with signature np.dtype((float64, 3)), np.dtype((float64, 3)) → np.dtype((float64, 3))

Another alternative to mention is returning multiple arrays, e.g., two arrays for a fixed dimension of size 2.

That said, I still think frozen dimension are a better proposal than either of these.

  • I’m -1 on optional dimensions: they seem to legitimize creating many overloads of gufuncs. I’m already not a fan of how matmul has special cases for lower dimensions that don’t generalize well. To me, the best way to handle matmul would be to use the proposed \_\_array\_function\_\_ to handle the shape-based special-case dispatching, either by:
    • Inserting dimensions, and calling the true gufunc np.linalg.matmul\_2d (which is a function I’d like direct access to anyway).
    • Dispatching to one of four ufuncs
I don't understand your alternative here. If we overload np.matmul using \_\_array\_function\_\_, then it would not use \*ether\* of these options for writing the operation in terms of other gufuncs. It would simply look for an \_\_array\_function\_\_ attribute, and call that method instead.

My concern with either inserting dimensions or dispatching to one of four ufuncs is that some objects (e.g., xarray.DataArray) define matrix multiplication, but in an incompatible way with NumPy (e.g., xarray sums over axes with the same name, instead of last / second-to-last axes). NumPy really ought to provide a way overload the either operation, without either inserting/removing dummy dimensions or inspecting input shapes to dispatch to other gufuncs.

That said, if you don't want to make np.matmul a gufunc, then I would much rather use Python's standard overloading rules with \_\_matmul\_\_/\_\_rmatmul\_\_ than use \_\_array\_function\_\_, for two reasons:
1\. You \*already\* need to use \_\_matmul\_\_/\_\_rmatmul\_\_ if you want to support matrix multiplication with @ on your class, so \_\_array\_function\_\_ would be additional and redundant. \_\_array\_function\_\_ is really intended as a fall-back, for cases where there is no other alternative.
2\. With the current \_\_array\_function\_\_ proposal, this would imply that calling other unimplemented NumPy functions on your object would raise TypeError rather than doing coercion. This sort of additional coupled behavior is probably not what an implementor of operator.matmul/@ is looking for.

In summary, I would either support:
1\. (This proposal) Adding additional optional dimensions to gufuncs for np.matmul/operator.matmul, or
2\. Making operator.matmul a special case for mathematical operators that always checks overloads with \_\_matmul\_\_/\_\_rmatmul\_\_ even if \_\_array\_ufunc\_\_ is defined.

Either way, matrix-multiplication becomes somewhat of a special case. It's just a matter of whether it's a special case for gufuncs (using optional dimensions) or a special case for arithmetic overloads in NumPy (not using \_\_array\_ufunc\_\_). Given that I think optional dimensions have other conceivable uses in gufuncs (for row/column vectors), I think that's the better option.

I would not support either expand dimensions or dispatch to multiple gufuncs in NumPy's implementation of operator.matmul (i.e., ndarray.\_\_matmul\_\_). We could potentially only do this for numpy.matmul rather than operator.matmul/@, but that opens the door to potential inconsistency between the NumPy version of an operator and Python's version of an operator, which is something we tried very hard to avoid with \_\_arary\_ufunc\_\_.