diff --git a/doc/release/1.16.0-notes.rst b/doc/release/1.16.0-notes.rst index ae21f4ffdf42..000c69cef074 100644 --- a/doc/release/1.16.0-notes.rst +++ b/doc/release/1.16.0-notes.rst @@ -47,6 +47,12 @@ Even when no elements needed to be drawn, ``np.random.randint`` and distribution. This has been fixed so that e.g. ``np.random.choice([], 0) == np.array([], dtype=float64)``. +``linalg.qr`` now works with empty matrices +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Previously, a ``LinAlgError`` would be raised when empty matrix +(with zero rows and/or columns) is passed in. This has been fixed +so that outputs of appropriate shapes are returned for the various modes. + ARM support updated ------------------- Support for ARM CPUs has been updated to accommodate 32 and 64 bit targets, diff --git a/numpy/linalg/linalg.py b/numpy/linalg/linalg.py index 8e7ad70cdbf8..a7217ccbacc3 100644 --- a/numpy/linalg/linalg.py +++ b/numpy/linalg/linalg.py @@ -858,13 +858,13 @@ def qr(a, mode='reduced'): a, wrap = _makearray(a) _assertRank2(a) - _assertNoEmpty2d(a) m, n = a.shape t, result_t = _commonType(a) a = _fastCopyAndTranspose(t, a) a = _to_native_byte_order(a) mn = min(m, n) tau = zeros((mn,), t) + if isComplexType(t): lapack_routine = lapack_lite.zgeqrf routine_name = 'zgeqrf' @@ -875,14 +875,14 @@ def qr(a, mode='reduced'): # calculate optimal size of work data 'work' lwork = 1 work = zeros((lwork,), t) - results = lapack_routine(m, n, a, m, tau, work, -1, 0) + results = lapack_routine(m, n, a, max(1, m), tau, work, -1, 0) if results['info'] != 0: raise LinAlgError('%s returns %d' % (routine_name, results['info'])) # do qr decomposition - lwork = int(abs(work[0])) + lwork = max(1, n, int(abs(work[0]))) work = zeros((lwork,), t) - results = lapack_routine(m, n, a, m, tau, work, lwork, 0) + results = lapack_routine(m, n, a, max(1, m), tau, work, lwork, 0) if results['info'] != 0: raise LinAlgError('%s returns %d' % (routine_name, results['info'])) @@ -918,14 +918,14 @@ def qr(a, mode='reduced'): # determine optimal lwork lwork = 1 work = zeros((lwork,), t) - results = lapack_routine(m, mc, mn, q, m, tau, work, -1, 0) + results = lapack_routine(m, mc, mn, q, max(1, m), tau, work, -1, 0) if results['info'] != 0: raise LinAlgError('%s returns %d' % (routine_name, results['info'])) # compute q - lwork = int(abs(work[0])) + lwork = max(1, n, int(abs(work[0]))) work = zeros((lwork,), t) - results = lapack_routine(m, mc, mn, q, m, tau, work, lwork, 0) + results = lapack_routine(m, mc, mn, q, max(1, m), tau, work, lwork, 0) if results['info'] != 0: raise LinAlgError('%s returns %d' % (routine_name, results['info'])) diff --git a/numpy/linalg/tests/test_linalg.py b/numpy/linalg/tests/test_linalg.py index 1c24f1e041f0..0df673884f6f 100644 --- a/numpy/linalg/tests/test_linalg.py +++ b/numpy/linalg/tests/test_linalg.py @@ -1582,9 +1582,25 @@ def check_qr(self, a): assert_(isinstance(r2, a_type)) assert_almost_equal(r2, r1) - def test_qr_empty(self): - a = np.zeros((0, 2)) - assert_raises(linalg.LinAlgError, linalg.qr, a) + + @pytest.mark.parametrize(["m", "n"], [ + (3, 0), + (0, 3), + (0, 0) + ]) + def test_qr_empty(self, m, n): + k = min(m, n) + a = np.empty((m, n)) + a_type = type(a) + a_dtype = a.dtype + + self.check_qr(a) + + h, tau = np.linalg.qr(a, mode='raw') + assert_equal(h.dtype, np.double) + assert_equal(tau.dtype, np.double) + assert_equal(h.shape, (n, m)) + assert_equal(tau.shape, (k,)) def test_mode_raw(self): # The factorization is not unique and varies between libraries, @@ -1625,15 +1641,6 @@ def test_mode_all_but_economic(self): self.check_qr(m2) self.check_qr(m2.T) - def test_0_size(self): - # There may be good ways to do (some of this) reasonably: - a = np.zeros((0, 0)) - assert_raises(linalg.LinAlgError, linalg.qr, a) - a = np.zeros((0, 1)) - assert_raises(linalg.LinAlgError, linalg.qr, a) - a = np.zeros((1, 0)) - assert_raises(linalg.LinAlgError, linalg.qr, a) - class TestCholesky(object): # TODO: are there no other tests for cholesky?