8000 ENH: handle empty matrices in qr decomposition by convexset · Pull Request #11593 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/release/1.16.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 7 additions & 7 deletions numpy/linalg/linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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)
Copy link
Member
@eric-wieser eric-wieser Jul 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exactly the fix I was expecting :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... yes... and (the late) Gene Golub is not going to tell me to give back my e-mail account. (I got my gmail invite from him.)

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']))

Expand Down Expand Up @@ -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])))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, looking at the source code - work[0] is populated as N*NB, and NB = 32`` - so strictly speaking the nis not needed here, Arguably, lapack ought to be settingwork[0]tomax(1, N*NB)`, so I'd consider this a bug in lapack.

Either way, what you have here is definitely correct, and it doesn't really do any harm to be extra safe.

8000
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']))

Expand Down
31 changes: 19 additions & 12 deletions numpy/linalg/tests/test_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
8AD7
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,
Expand Down Expand Up @@ -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?
Expand Down
0