Repository: numpy/numpy

Issue:
linalg.det throws LinAlgError for 0-by-0 matrices
Since LAPACK functions cannot handle 0-by-0 matrices, numpy must explicitly handle those cases.  However, while in newer versions of numpy, the inverse is computed correctly, the determinant raises `LinAlgError`:

```
>>> A = numpy.empty((0, 0))
>>> numpy.linalg.inv(A)
array([], shape=(0, 0), dtype=float64)
>>> numpy.linalg.det(A)
LinAlgError: Arrays cannot be empty
```

Since the determinant of a zero-by-zero matrix is well-defined, a LinAlgError is IMHO wrong, since it indicates a numerics problem rather than the purely technical fact that the backend cannot handle this.

Thus, expected result:

```
>>> numpy.linalg.det(A)
1.0
```

Numpy version: 1.11.0 (also present in git master)
Python version: 2.7.12



Discussion:
Hi! I'll take a look and see what I can do to fix this.  But just a heads up, this is my first time fixing a bug in numpy, so I might not be super quick.

Because of the ability to compute determinants of a stack of arrays this seems to get a little...messy.

```
>>> a = np.array([ [[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]] ])
>>> a.shape
(3, 2, 2)
>>> np.linalg.det(a)
array([-2., -3., -8.])
```

Now, my thought is to do the following: 

Loop over a and flag any arrays in the stack that are empty.   

Keep track of the indices where there are empty arrays for later.  
Delete the empty arrays, pass the nonempty arrays to _umath_linalg.det, read back the determinants, and then fill in the missing locations with ones where there were empty matrices.

Does this seem like it will address the issue?

All "stacked" arrays are always of the same shape, which can be
determined from the overall shape so no loops needed.

Hm, so if one member of a stacked array is empty, they are then all empty?  This doesn't seem to be explicitly forbidden by numpy:

```
>>> a = np.array( [ [[1, 2], [3,4]], [[]], [[]] ])
>>> a.shape
(3,)
```

@mhassell: That's a `dtype=object, ndim=1` array though, which `np.linalg.det` (rightly) rejects

@mhassell I think you may draw some inspiration from the code of `linalg.inv`, since it handles 0-by-0 just fine.

Sure, I'll check it out and try to fix this. Thanks for the pointer.

Hi, I'd like to know if this is still being worked on? I'd like to try my hand at it otherwise.
Hi Don86, I've gotten a bit swamped with other stuff, so please do have a go at it.
I've got this one, but I think there are a tonne more cases in `linalg` that ought to work for 0d

Resolving patch:
diff --git a/numpy/linalg/linalg.py b/numpy/linalg/linalg.py
--- a/numpy/linalg/linalg.py
+++ b/numpy/linalg/linalg.py
@@ -23,7 +23,7 @@
     csingle, cdouble, inexact, complexfloating, newaxis, ravel, all, Inf, dot,
     add, multiply, sqrt, maximum, fastCopyAndTranspose, sum, isfinite, size,
     finfo, errstate, geterrobj, longdouble, rollaxis, amin, amax, product, abs,
-    broadcast, atleast_2d, intp, asanyarray, isscalar, object_
+    broadcast, atleast_2d, intp, asanyarray, isscalar, object_, ones
     )
 from numpy.core.multiarray import normalize_axis_index
 from numpy.lib import triu, asfarray
@@ -217,9 +217,13 @@ def _assertFinite(*arrays):
         if not (isfinite(a).all()):
             raise LinAlgError("Array must not contain infs or NaNs")
 
+def _isEmpty2d(arr):
+    # check size first for efficiency
+    return arr.size == 0 and product(arr.shape[-2:]) == 0
+
 def _assertNoEmpty2d(*arrays):
     for a in arrays:
-        if a.size == 0 and product(a.shape[-2:]) == 0:
+        if _isEmpty2d(a):
             raise LinAlgError("Arrays cannot be empty")
 
 
@@ -898,11 +902,12 @@ def eigvals(a):
 
     """
     a, wrap = _makearray(a)
-    _assertNoEmpty2d(a)
     _assertRankAtLeast2(a)
     _assertNdSquareness(a)
     _assertFinite(a)
     t, result_t = _commonType(a)
+    if _isEmpty2d(a):
+        return empty(a.shape[-1:], dtype=result_t)
 
     extobj = get_linalg_error_extobj(
         _raise_linalgerror_eigenvalues_nonconvergence)
@@ -1002,10 +1007,11 @@ def eigvalsh(a, UPLO='L'):
         gufunc = _umath_linalg.eigvalsh_up
 
     a, wrap = _makearray(a)
-    _assertNoEmpty2d(a)
     _assertRankAtLeast2(a)
     _assertNdSquareness(a)
     t, result_t = _commonType(a)
+    if _isEmpty2d(a):
+        return empty(a.shape[-1:], dtype=result_t)
     signature = 'D->d' if isComplexType(t) else 'd->d'
     w = gufunc(a, signature=signature, extobj=extobj)
     return w.astype(_realType(result_t), copy=False)
@@ -1139,11 +1145,14 @@ def eig(a):
 
     """
     a, wrap = _makearray(a)
-    _assertNoEmpty2d(a)
     _assertRankAtLeast2(a)
     _assertNdSquareness(a)
     _assertFinite(a)
     t, result_t = _commonType(a)
+    if _isEmpty2d(a):
+        w = empty(a.shape[-1:], dtype=result_t)
+        vt = empty(a.shape, dtype=result_t)
+        return w, wrap(vt)
 
     extobj = get_linalg_error_extobj(
         _raise_linalgerror_eigenvalues_nonconvergence)
@@ -1280,8 +1289,11 @@ def eigh(a, UPLO='L'):
     a, wrap = _makearray(a)
     _assertRankAtLeast2(a)
     _assertNdSquareness(a)
-    _assertNoEmpty2d(a)
     t, result_t = _commonType(a)
+    if _isEmpty2d(a):
+        w = empty(a.shape[-1:], dtype=result_t)
+        vt = empty(a.shape, dtype=result_t)
+        return w, wrap(vt)
 
     extobj = get_linalg_error_extobj(
         _raise_linalgerror_eigenvalues_nonconvergence)
@@ -1660,7 +1672,9 @@ def pinv(a, rcond=1e-15 ):
 
     """
     a, wrap = _makearray(a)
-    _assertNoEmpty2d(a)
+    if _isEmpty2d(a):
+        res = empty(a.shape[:-2] + (a.shape[-1], a.shape[-2]), dtype=a.dtype)
+        return wrap(res)
     a = a.conjugate()
     u, s, vt = svd(a, 0)
     m = u.shape[0]
@@ -1751,11 +1765,15 @@ def slogdet(a):
 
     """
     a = asarray(a)
-    _assertNoEmpty2d(a)
     _assertRankAtLeast2(a)
     _assertNdSquareness(a)
     t, result_t = _commonType(a)
     real_t = _realType(result_t)
+    if _isEmpty2d(a):
+        # determinant of empty matrix is 1
+        sign = ones(a.shape[:-2], dtype=result_t)
+        logdet = zeros(a.shape[:-2], dtype=real_t)
+        return sign, logdet
     signature = 'D->Dd' if isComplexType(t) else 'd->dd'
     sign, logdet = _umath_linalg.slogdet(a, signature=signature)
     if isscalar(sign):
@@ -1816,10 +1834,12 @@ def det(a):
 
     """
     a = asarray(a)
-    _assertNoEmpty2d(a)
     _assertRankAtLeast2(a)
     _assertNdSquareness(a)
     t, result_t = _commonType(a)
+    # 0x0 matrices have determinant 1
+    if _isEmpty2d(a):
+        return ones(a.shape[:-2], dtype=result_t)
     signature = 'D->D' if isComplexType(t) else 'd->d'
     r = _umath_linalg.det(a, signature=signature)
     if isscalar(r):


Test patch:
diff --git a/numpy/linalg/tests/test_linalg.py b/numpy/linalg/tests/test_linalg.py
--- a/numpy/linalg/tests/test_linalg.py
+++ b/numpy/linalg/tests/test_linalg.py
@@ -127,7 +127,11 @@ def apply_tag(tag, cases):
                array([[2. + 1j, 1. + 2j, 1 + 3j], [1 - 2j, 1 - 3j, 1 - 6j]], dtype=cdouble)),
     LinalgCase("0x0",
                np.empty((0, 0), dtype=double),
-               np.empty((0, 0), dtype=double),
+               np.empty((0,), dtype=double),
+               tags={'size-0'}),
+    LinalgCase("0x0_matrix",
+               np.empty((0, 0), dtype=double).view(np.matrix),
+               np.empty((0, 1), dtype=double).view(np.matrix),
                tags={'size-0'}),
     LinalgCase("8x8",
                np.random.rand(8, 8),
@@ -549,9 +553,6 @@ class ArraySubclass(np.ndarray):
 class TestEigvals(LinalgSquareTestCase, LinalgGeneralizedSquareTestCase):
 
     def do(self, a, b, tags):
-        if 'size-0' in tags:
-            assert_raises(LinAlgError, linalg.eigvals, a)
-            return
         ev = linalg.eigvals(a)
         evalues, evectors = linalg.eig(a)
         assert_almost_equal(ev, evalues)
@@ -569,10 +570,6 @@ def check(dtype):
 class TestEig(LinalgSquareTestCase, LinalgGeneralizedSquareTestCase):
 
     def do(self, a, b, tags):
-        if 'size-0' in tags:
-            assert_raises(LinAlgError, linalg.eig, a)
-            return
-
         evalues, evectors = linalg.eig(a)
         assert_allclose(dot_generalized(a, evectors),
                         np.asarray(evectors) * np.asarray(evalues)[..., None, :],
@@ -667,9 +664,6 @@ def test(self):
 class TestPinv(LinalgSquareTestCase, LinalgNonsquareTestCase):
 
     def do(self, a, b, tags):
-        if 'size-0' in tags:
-            assert_raises(LinAlgError, linalg.pinv, a)
-            return
         a_ginv = linalg.pinv(a)
         # `a @ a_ginv == I` does not hold if a is singular
         assert_almost_equal(dot(a, a_ginv).dot(a), a, single_decimal=5, double_decimal=11)
@@ -679,9 +673,6 @@ def do(self, a, b, tags):
 class TestDet(LinalgSquareTestCase, LinalgGeneralizedSquareTestCase):
 
     def do(self, a, b, tags):
-        if 'size-0' in tags:
-            assert_raises(LinAlgError, linalg.det, a)
-            return
         d = linalg.det(a)
         (s, ld) = linalg.slogdet(a)
         if asarray(a).dtype.type in (single, double):
@@ -820,9 +811,6 @@ def test_square(self):
 class TestEigvalsh(HermitianTestCase, HermitianGeneralizedTestCase):
 
     def do(self, a, b, tags):
-        if 'size-0' in tags:
-            assert_raises(LinAlgError, linalg.eigvalsh, a, 'L')
-            return
         # note that eigenvalue arrays returned by eig must be sorted since
         # their order isn't guaranteed.
         ev = linalg.eigvalsh(a, 'L')
@@ -873,9 +861,6 @@ def test_UPLO(self):
 class TestEigh(HermitianTestCase, HermitianGeneralizedTestCase):
 
     def do(self, a, b, tags):
-        if 'size-0' in tags:
-            assert_raises(LinAlgError, linalg.eigh, a)
-            return
         # note that eigenvalue arrays returned by eig must be sorted since
         # their order isn't guaranteed.
         ev, evc = linalg.eigh(a)