Repository: scikit-hep/awkward

Issue:
`np.matmul` produces wrong result with NumPy RHS
The following multiplication produces a result with too few entries in the underlying contents, leading to errors when accessing items:
```python
transform = ak.Array(
    [
        [0, 1, 0],
        [1, 0, 0],
        [0, 0, 1],
    ]
)

vector = ak.Array(
    np.r_[4,5,6].reshape(3, 1)    
)

result = np.matmul(transform, vector)
result[0]
```

This does not happen if I create the `vector` Array by hand:
```python
vector = ak.Array(
    [
        [4],
        [5],
        [6]
    ]
)
result = np.matmul(transform, vector)
result[0]
```

This is also true of `transpose(vector) @ transform`

```python
>>> import awkward as ak
>>> ak.__version__
'1.2.2'
```



Discussion:

Resolving patch:
diff --git a/src/awkward/_connect/_numpy.py b/src/awkward/_connect/_numpy.py
--- a/src/awkward/_connect/_numpy.py
+++ b/src/awkward/_connect/_numpy.py
@@ -144,13 +144,13 @@ def getfunction(inputs):
         if custom is not None:
             return lambda: adjust(custom, inputs, kwargs)
 
-        inputs = [deregulate(x) for x in inputs]
-
         if ufunc is numpy.matmul:
             custom_matmul = getfunction_matmul(inputs)
             if custom_matmul is not None:
                 return custom_matmul
 
+        inputs = [deregulate(x) for x in inputs]
+
         if all(
             isinstance(x, ak.layout.NumpyArray)
             or not isinstance(x, (ak.layout.Content, ak.partition.PartitionedArray))
@@ -290,6 +290,13 @@ def matmul_for_numba(lefts, rights, dtype):
 
 
 def getfunction_matmul(inputs):
+    inputs = [
+        ak._util.recursively_apply(
+            x, (lambda _: _), pass_depth=False, numpy_to_regular=True
+        )
+        for x in inputs
+    ]
+
     if len(inputs) == 2 and all(
         isinstance(x, ak._util.listtypes)
         and isinstance(x.content, ak._util.listtypes)
diff --git a/src/awkward/_util.py b/src/awkward/_util.py
--- a/src/awkward/_util.py
+++ b/src/awkward/_util.py
@@ -563,7 +563,10 @@ def all_same_offsets(nplike, inputs):
                 ):
                     return False
             elif isinstance(x, ak.layout.RegularArray):
-                my_offsets = nplike.arange(0, len(x.content), x.size)
+                if x.size == 0:
+                    my_offsets = nplike.empty(0, dtype=np.int64)
+                else:
+                    my_offsets = nplike.arange(0, len(x.content), x.size)
                 if offsets is None:
                     offsets = my_offsets
                 elif not nplike.array_equal(offsets, my_offsets):


Test patch:
diff --git a/tests/test_0846-matrix-multiplication-numpy.py b/tests/test_0846-matrix-multiplication-numpy.py
new file mode 100644
--- /dev/null
+++ b/tests/test_0846-matrix-multiplication-numpy.py
@@ -0,0 +1,41 @@
+# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE
+
+from __future__ import absolute_import
+
+import pytest  # noqa: F401
+import numpy as np  # noqa: F401
+import awkward as ak  # noqa: F401
+
+pytest.importorskip("numba")
+
+
+def test_numpy_rhs():
+    transform = ak.Array(
+        [
+            [0, 1, 0],
+            [4, 0, 0],
+            [0, 0, 2],
+        ]
+    )
+
+    vector = ak.Array(np.r_[4, 5, 6].reshape(3, 1))
+
+    result = np.matmul(transform, vector)
+
+    assert result.tolist() == [[5], [16], [12]]
+
+
+def test_numpy_lhs():
+    transform = ak.Array(
+        [
+            [0, 4, 0],
+            [1, 0, 0],
+            [0, 0, 2],
+        ]
+    )
+
+    vector = ak.Array(np.r_[4, 5, 6].reshape(1, 3))
+
+    result = np.matmul(vector, transform)
+
+    assert result.tolist() == [[5, 16, 12]]