import itertools import numpy as np from numpy.testing import assert_, assert_allclose, assert_equal from pytest import raises as assert_raises from scipy import linalg import scipy.linalg._decomp_update as _decomp_update from scipy.linalg._decomp_update import qr_delete, qr_update, qr_insert def assert_unitary(a, rtol=None, atol=None, assert_sqr=True): if rtol is None: rtol = 10.0 ** -(np.finfo(a.dtype).precision-2) if atol is None: atol = 10*np.finfo(a.dtype).eps if assert_sqr: assert_(a.shape[0] == a.shape[1], 'unitary matrices must be square') aTa = np.dot(a.T.conj(), a) assert_allclose(aTa, np.eye(a.shape[1]), rtol=rtol, atol=atol) def assert_upper_tri(a, rtol=None, atol=None): if rtol is None: rtol = 10.0 ** -(np.finfo(a.dtype).precision-2) if atol is None: atol = 2*np.finfo(a.dtype).eps mask = np.tri(a.shape[0], a.shape[1], -1, np.bool_) assert_allclose(a[mask], 0.0, rtol=rtol, atol=atol) def check_qr(q, r, a, rtol, atol, assert_sqr=True): assert_unitary(q, rtol, atol, assert_sqr) assert_upper_tri(r, rtol, atol) assert_allclose(q.dot(r), a, rtol=rtol, atol=atol) def make_strided(arrs): strides = [(3, 7), (2, 2), (3, 4), (4, 2), (5, 4), (2, 3), (2, 1), (4, 5)] kmax = len(strides) k = 0 ret = [] for a in arrs: if a.ndim == 1: s = strides[k % kmax] k += 1 base = np.zeros(s[0]*a.shape[0]+s[1], a.dtype) view = base[s[1]::s[0]] view[...] = a elif a.ndim == 2: s = strides[k % kmax] t = strides[(k+1) % kmax] k += 2 base = np.zeros((s[0]*a.shape[0]+s[1], t[0]*a.shape[1]+t[1]), a.dtype) view = base[s[1]::s[0], t[1]::t[0]] view[...] = a else: raise ValueError('make_strided only works for ndim = 1 or' ' 2 arrays') ret.append(view) return ret def negate_strides(arrs): ret = [] for a in arrs: b = np.zeros_like(a) if b.ndim == 2: b = b[::-1, ::-1] elif b.ndim == 1: b = b[::-1] else: raise ValueError('negate_strides only works for ndim = 1 or' ' 2 arrays') b[...] = a ret.append(b) return ret def nonitemsize_strides(arrs): out = [] for a in arrs: a_dtype = a.dtype b = np.zeros(a.shape, [('a', a_dtype), ('junk', 'S1')]) c = b.getfield(a_dtype) c[...] = a out.append(c) return out def make_nonnative(arrs): return [a.astype(a.dtype.newbyteorder()) for a in arrs] class BaseQRdeltas: def setup_method(self): self.rtol = 10.0 ** -(np.finfo(self.dtype).precision-2) self.atol = 10 * np.finfo(self.dtype).eps def generate(self, type, mode='full'): np.random.seed(29382) shape = {'sqr': (8, 8), 'tall': (12, 7), 'fat': (7, 12), 'Mx1': (8, 1), '1xN': (1, 8), '1x1': (1, 1)}[type] a = np.random.random(shape) if np.iscomplexobj(self.dtype.type(1)): b = np.random.random(shape) a = a + 1j * b a = a.astype(self.dtype) q, r = linalg.qr(a, mode=mode) return a, q, r class BaseQRdelete(BaseQRdeltas): def test_sqr_1_row(self): a, q, r = self.generate('sqr') for row in range(r.shape[0]): q1, r1 = qr_delete(q, r, row, overwrite_qr=False) a1 = np.delete(a, row, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_sqr_p_row(self): a, q, r = self.generate('sqr') for ndel in range(2, 6): for row in range(a.shape[0]-ndel): q1, r1 = qr_delete(q, r, row, ndel, overwrite_qr=False) a1 = np.delete(a, slice(row, row+ndel), 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_sqr_1_col(self): a, q, r = self.generate('sqr') for col in range(r.shape[1]): q1, r1 = qr_delete(q, r, col, which='col', overwrite_qr=False) a1 = np.delete(a, col, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_sqr_p_col(self): a, q, r = self.generate('sqr') for ndel in range(2, 6): for col in range(r.shape[1]-ndel): q1, r1 = qr_delete(q, r, col, ndel, which='col', overwrite_qr=False) a1 = np.delete(a, slice(col, col+ndel), 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_tall_1_row(self): a, q, r = self.generate('tall') for row in range(r.shape[0]): q1, r1 = qr_delete(q, r, row, overwrite_qr=False) a1 = np.delete(a, row, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_tall_p_row(self): a, q, r = self.generate('tall') for ndel in range(2, 6): for row in range(a.shape[0]-ndel): q1, r1 = qr_delete(q, r, row, ndel, overwrite_qr=False) a1 = np.delete(a, slice(row, row+ndel), 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_tall_1_col(self): a, q, r = self.generate('tall') for col in range(r.shape[1]): q1, r1 = qr_delete(q, r, col, which='col', overwrite_qr=False) a1 = np.delete(a, col, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_tall_p_col(self): a, q, r = self.generate('tall') for ndel in range(2, 6): for col in range(r.shape[1]-ndel): q1, r1 = qr_delete(q, r, col, ndel, which='col', overwrite_qr=False) a1 = np.delete(a, slice(col, col+ndel), 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_fat_1_row(self): a, q, r = self.generate('fat') for row in range(r.shape[0]): q1, r1 = qr_delete(q, r, row, overwrite_qr=False) a1 = np.delete(a, row, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_fat_p_row(self): a, q, r = self.generate('fat') for ndel in range(2, 6): for row in range(a.shape[0]-ndel): q1, r1 = qr_delete(q, r, row, ndel, overwrite_qr=False) a1 = np.delete(a, slice(row, row+ndel), 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_fat_1_col(self): a, q, r = self.generate('fat') for col in range(r.shape[1]): q1, r1 = qr_delete(q, r, col, which='col', overwrite_qr=False) a1 = np.delete(a, col, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_fat_p_col(self): a, q, r = self.generate('fat') for ndel in range(2, 6): for col in range(r.shape[1]-ndel): q1, r1 = qr_delete(q, r, col, ndel, which='col', overwrite_qr=False) a1 = np.delete(a, slice(col, col+ndel), 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_economic_1_row(self): # this test always starts and ends with an economic decomp. a, q, r = self.generate('tall', 'economic') for row in range(r.shape[0]): q1, r1 = qr_delete(q, r, row, overwrite_qr=False) a1 = np.delete(a, row, 0) check_qr(q1, r1, a1, self.rtol, self.atol, False) # for economic row deletes # eco - prow = eco # eco - prow = sqr # eco - prow = fat def base_economic_p_row_xxx(self, ndel): a, q, r = self.generate('tall', 'economic') for row in range(a.shape[0]-ndel): q1, r1 = qr_delete(q, r, row, ndel, overwrite_qr=False) a1 = np.delete(a, slice(row, row+ndel), 0) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_economic_p_row_economic(self): # (12, 7) - (3, 7) = (9,7) --> stays economic self.base_economic_p_row_xxx(3) def test_economic_p_row_sqr(self): # (12, 7) - (5, 7) = (7, 7) --> becomes square self.base_economic_p_row_xxx(5) def test_economic_p_row_fat(self): # (12, 7) - (7,7) = (5, 7) --> becomes fat self.base_economic_p_row_xxx(7) def test_economic_1_col(self): a, q, r = self.generate('tall', 'economic') for col in range(r.shape[1]): q1, r1 = qr_delete(q, r, col, which='col', overwrite_qr=False) a1 = np.delete(a, col, 1) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_economic_p_col(self): a, q, r = self.generate('tall', 'economic') for ndel in range(2, 6): for col in range(r.shape[1]-ndel): q1, r1 = qr_delete(q, r, col, ndel, which='col', overwrite_qr=False) a1 = np.delete(a, slice(col, col+ndel), 1) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_Mx1_1_row(self): a, q, r = self.generate('Mx1') for row in range(r.shape[0]): q1, r1 = qr_delete(q, r, row, overwrite_qr=False) a1 = np.delete(a, row, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_Mx1_p_row(self): a, q, r = self.generate('Mx1') for ndel in range(2, 6): for row in range(a.shape[0]-ndel): q1, r1 = qr_delete(q, r, row, ndel, overwrite_qr=False) a1 = np.delete(a, slice(row, row+ndel), 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1xN_1_col(self): a, q, r = self.generate('1xN') for col in range(r.shape[1]): q1, r1 = qr_delete(q, r, col, which='col', overwrite_qr=False) a1 = np.delete(a, col, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1xN_p_col(self): a, q, r = self.generate('1xN') for ndel in range(2, 6): for col in range(r.shape[1]-ndel): q1, r1 = qr_delete(q, r, col, ndel, which='col', overwrite_qr=False) a1 = np.delete(a, slice(col, col+ndel), 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_Mx1_economic_1_row(self): a, q, r = self.generate('Mx1', 'economic') for row in range(r.shape[0]): q1, r1 = qr_delete(q, r, row, overwrite_qr=False) a1 = np.delete(a, row, 0) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_Mx1_economic_p_row(self): a, q, r = self.generate('Mx1', 'economic') for ndel in range(2, 6): for row in range(a.shape[0]-ndel): q1, r1 = qr_delete(q, r, row, ndel, overwrite_qr=False) a1 = np.delete(a, slice(row, row+ndel), 0) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_delete_last_1_row(self): # full and eco are the same for 1xN a, q, r = self.generate('1xN') q1, r1 = qr_delete(q, r, 0, 1, 'row') assert_equal(q1, np.ndarray(shape=(0, 0), dtype=q.dtype)) assert_equal(r1, np.ndarray(shape=(0, r.shape[1]), dtype=r.dtype)) def test_delete_last_p_row(self): a, q, r = self.generate('tall', 'full') q1, r1 = qr_delete(q, r, 0, a.shape[0], 'row') assert_equal(q1, np.ndarray(shape=(0, 0), dtype=q.dtype)) assert_equal(r1, np.ndarray(shape=(0, r.shape[1]), dtype=r.dtype)) a, q, r = self.generate('tall', 'economic') q1, r1 = qr_delete(q, r, 0, a.shape[0], 'row') assert_equal(q1, np.ndarray(shape=(0, 0), dtype=q.dtype)) assert_equal(r1, np.ndarray(shape=(0, r.shape[1]), dtype=r.dtype)) def test_delete_last_1_col(self): a, q, r = self.generate('Mx1', 'economic') q1, r1 = qr_delete(q, r, 0, 1, 'col') assert_equal(q1, np.ndarray(shape=(q.shape[0], 0), dtype=q.dtype)) assert_equal(r1, np.ndarray(shape=(0, 0), dtype=r.dtype)) a, q, r = self.generate('Mx1', 'full') q1, r1 = qr_delete(q, r, 0, 1, 'col') assert_unitary(q1) assert_(q1.dtype == q.dtype) assert_(q1.shape == q.shape) assert_equal(r1, np.ndarray(shape=(r.shape[0], 0), dtype=r.dtype)) def test_delete_last_p_col(self): a, q, r = self.generate('tall', 'full') q1, r1 = qr_delete(q, r, 0, a.shape[1], 'col') assert_unitary(q1) assert_(q1.dtype == q.dtype) assert_(q1.shape == q.shape) assert_equal(r1, np.ndarray(shape=(r.shape[0], 0), dtype=r.dtype)) a, q, r = self.generate('tall', 'economic') q1, r1 = qr_delete(q, r, 0, a.shape[1], 'col') assert_equal(q1, np.ndarray(shape=(q.shape[0], 0), dtype=q.dtype)) assert_equal(r1, np.ndarray(shape=(0, 0), dtype=r.dtype)) def test_delete_1x1_row_col(self): a, q, r = self.generate('1x1') q1, r1 = qr_delete(q, r, 0, 1, 'row') assert_equal(q1, np.ndarray(shape=(0, 0), dtype=q.dtype)) assert_equal(r1, np.ndarray(shape=(0, r.shape[1]), dtype=r.dtype)) a, q, r = self.generate('1x1') q1, r1 = qr_delete(q, r, 0, 1, 'col') assert_unitary(q1) assert_(q1.dtype == q.dtype) assert_(q1.shape == q.shape) assert_equal(r1, np.ndarray(shape=(r.shape[0], 0), dtype=r.dtype)) # all full qr, row deletes and single column deletes should be able to # handle any non negative strides. (only row and column vector # operations are used.) p column delete require fortran ordered # Q and R and will make a copy as necessary. Economic qr row deletes # requre a contigous q. def base_non_simple_strides(self, adjust_strides, ks, p, which, overwriteable): if which == 'row': qind = (slice(p,None), slice(p,None)) rind = (slice(p,None), slice(None)) else: qind = (slice(None), slice(None)) rind = (slice(None), slice(None,-p)) for type, k in itertools.product(['sqr', 'tall', 'fat'], ks): a, q0, r0, = self.generate(type) qs, rs = adjust_strides((q0, r0)) if p == 1: a1 = np.delete(a, k, 0 if which == 'row' else 1) else: s = slice(k,k+p) if k < 0: s = slice(k, k + p + (a.shape[0] if which == 'row' else a.shape[1])) a1 = np.delete(a, s, 0 if which == 'row' else 1) # for each variable, q, r we try with it strided and # overwrite=False. Then we try with overwrite=True, and make # sure that q and r are still overwritten. q = q0.copy('F') r = r0.copy('F') q1, r1 = qr_delete(qs, r, k, p, which, False) check_qr(q1, r1, a1, self.rtol, self.atol) q1o, r1o = qr_delete(qs, r, k, p, which, True) check_qr(q1o, r1o, a1, self.rtol, self.atol) if overwriteable: assert_allclose(q1o, qs[qind], rtol=self.rtol, atol=self.atol) assert_allclose(r1o, r[rind], rtol=self.rtol, atol=self.atol) q = q0.copy('F') r = r0.copy('F') q2, r2 = qr_delete(q, rs, k, p, which, False) check_qr(q2, r2, a1, self.rtol, self.atol) q2o, r2o = qr_delete(q, rs, k, p, which, True) check_qr(q2o, r2o, a1, self.rtol, self.atol) if overwriteable: assert_allclose(q2o, q[qind], rtol=self.rtol, atol=self.atol) assert_allclose(r2o, rs[rind], rtol=self.rtol, atol=self.atol) q = q0.copy('F') r = r0.copy('F') # since some of these were consumed above qs, rs = adjust_strides((q, r)) q3, r3 = qr_delete(qs, rs, k, p, which, False) check_qr(q3, r3, a1, self.rtol, self.atol) q3o, r3o = qr_delete(qs, rs, k, p, which, True) check_qr(q3o, r3o, a1, self.rtol, self.atol) if overwriteable: assert_allclose(q2o, qs[qind], rtol=self.rtol, atol=self.atol) assert_allclose(r3o, rs[rind], rtol=self.rtol, atol=self.atol) def test_non_unit_strides_1_row(self): self.base_non_simple_strides(make_strided, [0], 1, 'row', True) def test_non_unit_strides_p_row(self): self.base_non_simple_strides(make_strided, [0], 3, 'row', True) def test_non_unit_strides_1_col(self): self.base_non_simple_strides(make_strided, [0], 1, 'col', True) def test_non_unit_strides_p_col(self): self.base_non_simple_strides(make_strided, [0], 3, 'col', False) def test_neg_strides_1_row(self): self.base_non_simple_strides(negate_strides, [0], 1, 'row', False) def test_neg_strides_p_row(self): self.base_non_simple_strides(negate_strides, [0], 3, 'row', False) def test_neg_strides_1_col(self): self.base_non_simple_strides(negate_strides, [0], 1, 'col', False) def test_neg_strides_p_col(self): self.base_non_simple_strides(negate_strides, [0], 3, 'col', False) def test_non_itemize_strides_1_row(self): self.base_non_simple_strides(nonitemsize_strides, [0], 1, 'row', False) def test_non_itemize_strides_p_row(self): self.base_non_simple_strides(nonitemsize_strides, [0], 3, 'row', False) def test_non_itemize_strides_1_col(self): self.base_non_simple_strides(nonitemsize_strides, [0], 1, 'col', False) def test_non_itemize_strides_p_col(self): self.base_non_simple_strides(nonitemsize_strides, [0], 3, 'col', False) def test_non_native_byte_order_1_row(self): self.base_non_simple_strides(make_nonnative, [0], 1, 'row', False) def test_non_native_byte_order_p_row(self): self.base_non_simple_strides(make_nonnative, [0], 3, 'row', False) def test_non_native_byte_order_1_col(self): self.base_non_simple_strides(make_nonnative, [0], 1, 'col', False) def test_non_native_byte_order_p_col(self): self.base_non_simple_strides(make_nonnative, [0], 3, 'col', False) def test_neg_k(self): a, q, r = self.generate('sqr') for k, p, w in itertools.product([-3, -7], [1, 3], ['row', 'col']): q1, r1 = qr_delete(q, r, k, p, w, overwrite_qr=False) if w == 'row': a1 = np.delete(a, slice(k+a.shape[0], k+p+a.shape[0]), 0) else: a1 = np.delete(a, slice(k+a.shape[0], k+p+a.shape[1]), 1) check_qr(q1, r1, a1, self.rtol, self.atol) def base_overwrite_qr(self, which, p, test_C, test_F, mode='full'): assert_sqr = True if mode == 'full' else False if which == 'row': qind = (slice(p,None), slice(p,None)) rind = (slice(p,None), slice(None)) else: qind = (slice(None), slice(None)) rind = (slice(None), slice(None,-p)) a, q0, r0 = self.generate('sqr', mode) if p == 1: a1 = np.delete(a, 3, 0 if which == 'row' else 1) else: a1 = np.delete(a, slice(3, 3+p), 0 if which == 'row' else 1) # don't overwrite q = q0.copy('F') r = r0.copy('F') q1, r1 = qr_delete(q, r, 3, p, which, False) check_qr(q1, r1, a1, self.rtol, self.atol, assert_sqr) check_qr(q, r, a, self.rtol, self.atol, assert_sqr) if test_F: q = q0.copy('F') r = r0.copy('F') q2, r2 = qr_delete(q, r, 3, p, which, True) check_qr(q2, r2, a1, self.rtol, self.atol, assert_sqr) # verify the overwriting assert_allclose(q2, q[qind], rtol=self.rtol, atol=self.atol) assert_allclose(r2, r[rind], rtol=self.rtol, atol=self.atol) if test_C: q = q0.copy('C') r = r0.copy('C') q3, r3 = qr_delete(q, r, 3, p, which, True) check_qr(q3, r3, a1, self.rtol, self.atol, assert_sqr) assert_allclose(q3, q[qind], rtol=self.rtol, atol=self.atol) assert_allclose(r3, r[rind], rtol=self.rtol, atol=self.atol) def test_overwrite_qr_1_row(self): # any positively strided q and r. self.base_overwrite_qr('row', 1, True, True) def test_overwrite_economic_qr_1_row(self): # Any contiguous q and positively strided r. self.base_overwrite_qr('row', 1, True, True, 'economic') def test_overwrite_qr_1_col(self): # any positively strided q and r. # full and eco share code paths self.base_overwrite_qr('col', 1, True, True) def test_overwrite_qr_p_row(self): # any positively strided q and r. self.base_overwrite_qr('row', 3, True, True) def test_overwrite_economic_qr_p_row(self): # any contiguous q and positively strided r self.base_overwrite_qr('row', 3, True, True, 'economic') def test_overwrite_qr_p_col(self): # only F orderd q and r can be overwritten for cols # full and eco share code paths self.base_overwrite_qr('col', 3, False, True) def test_bad_which(self): a, q, r = self.generate('sqr') assert_raises(ValueError, qr_delete, q, r, 0, which='foo') def test_bad_k(self): a, q, r = self.generate('tall') assert_raises(ValueError, qr_delete, q, r, q.shape[0], 1) assert_raises(ValueError, qr_delete, q, r, -q.shape[0]-1, 1) assert_raises(ValueError, qr_delete, q, r, r.shape[0], 1, 'col') assert_raises(ValueError, qr_delete, q, r, -r.shape[0]-1, 1, 'col') def test_bad_p(self): a, q, r = self.generate('tall') # p must be positive assert_raises(ValueError, qr_delete, q, r, 0, -1) assert_raises(ValueError, qr_delete, q, r, 0, -1, 'col') # and nonzero assert_raises(ValueError, qr_delete, q, r, 0, 0) assert_raises(ValueError, qr_delete, q, r, 0, 0, 'col') # must have at least k+p rows or cols, depending. assert_raises(ValueError, qr_delete, q, r, 3, q.shape[0]-2) assert_raises(ValueError, qr_delete, q, r, 3, r.shape[1]-2, 'col') def test_empty_q(self): a, q, r = self.generate('tall') # same code path for 'row' and 'col' assert_raises(ValueError, qr_delete, np.array([]), r, 0, 1) def test_empty_r(self): a, q, r = self.generate('tall') # same code path for 'row' and 'col' assert_raises(ValueError, qr_delete, q, np.array([]), 0, 1) def test_mismatched_q_and_r(self): a, q, r = self.generate('tall') r = r[1:] assert_raises(ValueError, qr_delete, q, r, 0, 1) def test_unsupported_dtypes(self): dts = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float16', 'longdouble', 'longcomplex', 'bool'] a, q0, r0 = self.generate('tall') for dtype in dts: q = q0.real.astype(dtype) with np.errstate(invalid="ignore"): r = r0.real.astype(dtype) assert_raises(ValueError, qr_delete, q, r0, 0, 1, 'row') assert_raises(ValueError, qr_delete, q, r0, 0, 2, 'row') assert_raises(ValueError, qr_delete, q, r0, 0, 1, 'col') assert_raises(ValueError, qr_delete, q, r0, 0, 2, 'col') assert_raises(ValueError, qr_delete, q0, r, 0, 1, 'row') assert_raises(ValueError, qr_delete, q0, r, 0, 2, 'row') assert_raises(ValueError, qr_delete, q0, r, 0, 1, 'col') assert_raises(ValueError, qr_delete, q0, r, 0, 2, 'col') def test_check_finite(self): a0, q0, r0 = self.generate('tall') q = q0.copy('F') q[1,1] = np.nan assert_raises(ValueError, qr_delete, q, r0, 0, 1, 'row') assert_raises(ValueError, qr_delete, q, r0, 0, 3, 'row') assert_raises(ValueError, qr_delete, q, r0, 0, 1, 'col') assert_raises(ValueError, qr_delete, q, r0, 0, 3, 'col') r = r0.copy('F') r[1,1] = np.nan assert_raises(ValueError, qr_delete, q0, r, 0, 1, 'row') assert_raises(ValueError, qr_delete, q0, r, 0, 3, 'row') assert_raises(ValueError, qr_delete, q0, r, 0, 1, 'col') assert_raises(ValueError, qr_delete, q0, r, 0, 3, 'col') def test_qr_scalar(self): a, q, r = self.generate('1x1') assert_raises(ValueError, qr_delete, q[0, 0], r, 0, 1, 'row') assert_raises(ValueError, qr_delete, q, r[0, 0], 0, 1, 'row') assert_raises(ValueError, qr_delete, q[0, 0], r, 0, 1, 'col') assert_raises(ValueError, qr_delete, q, r[0, 0], 0, 1, 'col') class TestQRdelete_f(BaseQRdelete): dtype = np.dtype('f') class TestQRdelete_F(BaseQRdelete): dtype = np.dtype('F') class TestQRdelete_d(BaseQRdelete): dtype = np.dtype('d') class TestQRdelete_D(BaseQRdelete): dtype = np.dtype('D') class BaseQRinsert(BaseQRdeltas): def generate(self, type, mode='full', which='row', p=1): a, q, r = super().generate(type, mode) assert_(p > 0) # super call set the seed... if which == 'row': if p == 1: u = np.random.random(a.shape[1]) else: u = np.random.random((p, a.shape[1])) elif which == 'col': if p == 1: u = np.random.random(a.shape[0]) else: u = np.random.random((a.shape[0], p)) else: ValueError('which should be either "row" or "col"') if np.iscomplexobj(self.dtype.type(1)): b = np.random.random(u.shape) u = u + 1j * b u = u.astype(self.dtype) return a, q, r, u def test_sqr_1_row(self): a, q, r, u = self.generate('sqr', which='row') for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, row, u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_sqr_p_row(self): # sqr + rows --> fat always a, q, r, u = self.generate('sqr', which='row', p=3) for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, np.full(3, row, np.intp), u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_sqr_1_col(self): a, q, r, u = self.generate('sqr', which='col') for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, col, u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_sqr_p_col(self): # sqr + cols --> fat always a, q, r, u = self.generate('sqr', which='col', p=3) for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, np.full(3, col, np.intp), u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_tall_1_row(self): a, q, r, u = self.generate('tall', which='row') for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, row, u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_tall_p_row(self): # tall + rows --> tall always a, q, r, u = self.generate('tall', which='row', p=3) for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, np.full(3, row, np.intp), u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_tall_1_col(self): a, q, r, u = self.generate('tall', which='col') for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, col, u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) # for column adds to tall matrices there are three cases to test # tall + pcol --> tall # tall + pcol --> sqr # tall + pcol --> fat def base_tall_p_col_xxx(self, p): a, q, r, u = self.generate('tall', which='col', p=p) for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, np.full(p, col, np.intp), u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_tall_p_col_tall(self): # 12x7 + 12x3 = 12x10 --> stays tall self.base_tall_p_col_xxx(3) def test_tall_p_col_sqr(self): # 12x7 + 12x5 = 12x12 --> becomes sqr self.base_tall_p_col_xxx(5) def test_tall_p_col_fat(self): # 12x7 + 12x7 = 12x14 --> becomes fat self.base_tall_p_col_xxx(7) def test_fat_1_row(self): a, q, r, u = self.generate('fat', which='row') for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, row, u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) # for row adds to fat matrices there are three cases to test # fat + prow --> fat # fat + prow --> sqr # fat + prow --> tall def base_fat_p_row_xxx(self, p): a, q, r, u = self.generate('fat', which='row', p=p) for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, np.full(p, row, np.intp), u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_fat_p_row_fat(self): # 7x12 + 3x12 = 10x12 --> stays fat self.base_fat_p_row_xxx(3) def test_fat_p_row_sqr(self): # 7x12 + 5x12 = 12x12 --> becomes sqr self.base_fat_p_row_xxx(5) def test_fat_p_row_tall(self): # 7x12 + 7x12 = 14x12 --> becomes tall self.base_fat_p_row_xxx(7) def test_fat_1_col(self): a, q, r, u = self.generate('fat', which='col') for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, col, u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_fat_p_col(self): # fat + cols --> fat always a, q, r, u = self.generate('fat', which='col', p=3) for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, np.full(3, col, np.intp), u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_economic_1_row(self): a, q, r, u = self.generate('tall', 'economic', 'row') for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row, overwrite_qru=False) a1 = np.insert(a, row, u, 0) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_economic_p_row(self): # tall + rows --> tall always a, q, r, u = self.generate('tall', 'economic', 'row', 3) for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row, overwrite_qru=False) a1 = np.insert(a, np.full(3, row, np.intp), u, 0) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_economic_1_col(self): a, q, r, u = self.generate('tall', 'economic', which='col') for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u.copy(), col, 'col', overwrite_qru=False) a1 = np.insert(a, col, u, 1) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_economic_1_col_bad_update(self): # When the column to be added lies in the span of Q, the update is # not meaningful. This is detected, and a LinAlgError is issued. q = np.eye(5, 3, dtype=self.dtype) r = np.eye(3, dtype=self.dtype) u = np.array([1, 0, 0, 0, 0], self.dtype) assert_raises(linalg.LinAlgError, qr_insert, q, r, u, 0, 'col') # for column adds to economic matrices there are three cases to test # eco + pcol --> eco # eco + pcol --> sqr # eco + pcol --> fat def base_economic_p_col_xxx(self, p): a, q, r, u = self.generate('tall', 'economic', which='col', p=p) for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, np.full(p, col, np.intp), u, 1) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_economic_p_col_eco(self): # 12x7 + 12x3 = 12x10 --> stays eco self.base_economic_p_col_xxx(3) def test_economic_p_col_sqr(self): # 12x7 + 12x5 = 12x12 --> becomes sqr self.base_economic_p_col_xxx(5) def test_economic_p_col_fat(self): # 12x7 + 12x7 = 12x14 --> becomes fat self.base_economic_p_col_xxx(7) def test_Mx1_1_row(self): a, q, r, u = self.generate('Mx1', which='row') for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, row, u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_Mx1_p_row(self): a, q, r, u = self.generate('Mx1', which='row', p=3) for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, np.full(3, row, np.intp), u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_Mx1_1_col(self): a, q, r, u = self.generate('Mx1', which='col') for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, col, u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_Mx1_p_col(self): a, q, r, u = self.generate('Mx1', which='col', p=3) for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, np.full(3, col, np.intp), u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_Mx1_economic_1_row(self): a, q, r, u = self.generate('Mx1', 'economic', 'row') for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, row, u, 0) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_Mx1_economic_p_row(self): a, q, r, u = self.generate('Mx1', 'economic', 'row', 3) for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, np.full(3, row, np.intp), u, 0) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_Mx1_economic_1_col(self): a, q, r, u = self.generate('Mx1', 'economic', 'col') for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, col, u, 1) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_Mx1_economic_p_col(self): a, q, r, u = self.generate('Mx1', 'economic', 'col', 3) for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, np.full(3, col, np.intp), u, 1) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_1xN_1_row(self): a, q, r, u = self.generate('1xN', which='row') for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, row, u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1xN_p_row(self): a, q, r, u = self.generate('1xN', which='row', p=3) for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, np.full(3, row, np.intp), u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1xN_1_col(self): a, q, r, u = self.generate('1xN', which='col') for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, col, u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1xN_p_col(self): a, q, r, u = self.generate('1xN', which='col', p=3) for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, np.full(3, col, np.intp), u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1x1_1_row(self): a, q, r, u = self.generate('1x1', which='row') for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, row, u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1x1_p_row(self): a, q, r, u = self.generate('1x1', which='row', p=3) for row in range(r.shape[0] + 1): q1, r1 = qr_insert(q, r, u, row) a1 = np.insert(a, np.full(3, row, np.intp), u, 0) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1x1_1_col(self): a, q, r, u = self.generate('1x1', which='col') for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, col, u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1x1_p_col(self): a, q, r, u = self.generate('1x1', which='col', p=3) for col in range(r.shape[1] + 1): q1, r1 = qr_insert(q, r, u, col, 'col', overwrite_qru=False) a1 = np.insert(a, np.full(3, col, np.intp), u, 1) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1x1_1_scalar(self): a, q, r, u = self.generate('1x1', which='row') assert_raises(ValueError, qr_insert, q[0, 0], r, u, 0, 'row') assert_raises(ValueError, qr_insert, q, r[0, 0], u, 0, 'row') assert_raises(ValueError, qr_insert, q, r, u[0], 0, 'row') assert_raises(ValueError, qr_insert, q[0, 0], r, u, 0, 'col') assert_raises(ValueError, qr_insert, q, r[0, 0], u, 0, 'col') assert_raises(ValueError, qr_insert, q, r, u[0], 0, 'col') def base_non_simple_strides(self, adjust_strides, k, p, which): for type in ['sqr', 'tall', 'fat']: a, q0, r0, u0 = self.generate(type, which=which, p=p) qs, rs, us = adjust_strides((q0, r0, u0)) if p == 1: ai = np.insert(a, k, u0, 0 if which == 'row' else 1) else: ai = np.insert(a, np.full(p, k, np.intp), u0 if which == 'row' else u0, 0 if which == 'row' else 1) # for each variable, q, r, u we try with it strided and # overwrite=False. Then we try with overwrite=True. Nothing # is checked to see if it can be overwritten, since only # F ordered Q can be overwritten when adding columns. q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') q1, r1 = qr_insert(qs, r, u, k, which, overwrite_qru=False) check_qr(q1, r1, ai, self.rtol, self.atol) q1o, r1o = qr_insert(qs, r, u, k, which, overwrite_qru=True) check_qr(q1o, r1o, ai, self.rtol, self.atol) q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') q2, r2 = qr_insert(q, rs, u, k, which, overwrite_qru=False) check_qr(q2, r2, ai, self.rtol, self.atol) q2o, r2o = qr_insert(q, rs, u, k, which, overwrite_qru=True) check_qr(q2o, r2o, ai, self.rtol, self.atol) q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') q3, r3 = qr_insert(q, r, us, k, which, overwrite_qru=False) check_qr(q3, r3, ai, self.rtol, self.atol) q3o, r3o = qr_insert(q, r, us, k, which, overwrite_qru=True) check_qr(q3o, r3o, ai, self.rtol, self.atol) q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') # since some of these were consumed above qs, rs, us = adjust_strides((q, r, u)) q5, r5 = qr_insert(qs, rs, us, k, which, overwrite_qru=False) check_qr(q5, r5, ai, self.rtol, self.atol) q5o, r5o = qr_insert(qs, rs, us, k, which, overwrite_qru=True) check_qr(q5o, r5o, ai, self.rtol, self.atol) def test_non_unit_strides_1_row(self): self.base_non_simple_strides(make_strided, 0, 1, 'row') def test_non_unit_strides_p_row(self): self.base_non_simple_strides(make_strided, 0, 3, 'row') def test_non_unit_strides_1_col(self): self.base_non_simple_strides(make_strided, 0, 1, 'col') def test_non_unit_strides_p_col(self): self.base_non_simple_strides(make_strided, 0, 3, 'col') def test_neg_strides_1_row(self): self.base_non_simple_strides(negate_strides, 0, 1, 'row') def test_neg_strides_p_row(self): self.base_non_simple_strides(negate_strides, 0, 3, 'row') def test_neg_strides_1_col(self): self.base_non_simple_strides(negate_strides, 0, 1, 'col') def test_neg_strides_p_col(self): self.base_non_simple_strides(negate_strides, 0, 3, 'col') def test_non_itemsize_strides_1_row(self): self.base_non_simple_strides(nonitemsize_strides, 0, 1, 'row') def test_non_itemsize_strides_p_row(self): self.base_non_simple_strides(nonitemsize_strides, 0, 3, 'row') def test_non_itemsize_strides_1_col(self): self.base_non_simple_strides(nonitemsize_strides, 0, 1, 'col') def test_non_itemsize_strides_p_col(self): self.base_non_simple_strides(nonitemsize_strides, 0, 3, 'col') def test_non_native_byte_order_1_row(self): self.base_non_simple_strides(make_nonnative, 0, 1, 'row') def test_non_native_byte_order_p_row(self): self.base_non_simple_strides(make_nonnative, 0, 3, 'row') def test_non_native_byte_order_1_col(self): self.base_non_simple_strides(make_nonnative, 0, 1, 'col') def test_non_native_byte_order_p_col(self): self.base_non_simple_strides(make_nonnative, 0, 3, 'col') def test_overwrite_qu_rank_1(self): # when inserting rows, the size of both Q and R change, so only # column inserts can overwrite q. Only complex column inserts # with C ordered Q overwrite u. Any contiguous Q is overwritten # when inserting 1 column a, q0, r, u, = self.generate('sqr', which='col', p=1) q = q0.copy('C') u0 = u.copy() # don't overwrite q1, r1 = qr_insert(q, r, u, 0, 'col', overwrite_qru=False) a1 = np.insert(a, 0, u0, 1) check_qr(q1, r1, a1, self.rtol, self.atol) check_qr(q, r, a, self.rtol, self.atol) # try overwriting q2, r2 = qr_insert(q, r, u, 0, 'col', overwrite_qru=True) check_qr(q2, r2, a1, self.rtol, self.atol) # verify the overwriting assert_allclose(q2, q, rtol=self.rtol, atol=self.atol) assert_allclose(u, u0.conj(), self.rtol, self.atol) # now try with a fortran ordered Q qF = q0.copy('F') u1 = u0.copy() q3, r3 = qr_insert(qF, r, u1, 0, 'col', overwrite_qru=False) check_qr(q3, r3, a1, self.rtol, self.atol) check_qr(qF, r, a, self.rtol, self.atol) # try overwriting q4, r4 = qr_insert(qF, r, u1, 0, 'col', overwrite_qru=True) check_qr(q4, r4, a1, self.rtol, self.atol) assert_allclose(q4, qF, rtol=self.rtol, atol=self.atol) def test_overwrite_qu_rank_p(self): # when inserting rows, the size of both Q and R change, so only # column inserts can potentially overwrite Q. In practice, only # F ordered Q are overwritten with a rank p update. a, q0, r, u, = self.generate('sqr', which='col', p=3) q = q0.copy('F') a1 = np.insert(a, np.zeros(3, np.intp), u, 1) # don't overwrite q1, r1 = qr_insert(q, r, u, 0, 'col', overwrite_qru=False) check_qr(q1, r1, a1, self.rtol, self.atol) check_qr(q, r, a, self.rtol, self.atol) # try overwriting q2, r2 = qr_insert(q, r, u, 0, 'col', overwrite_qru=True) check_qr(q2, r2, a1, self.rtol, self.atol) assert_allclose(q2, q, rtol=self.rtol, atol=self.atol) def test_empty_inputs(self): a, q, r, u = self.generate('sqr', which='row') assert_raises(ValueError, qr_insert, np.array([]), r, u, 0, 'row') assert_raises(ValueError, qr_insert, q, np.array([]), u, 0, 'row') assert_raises(ValueError, qr_insert, q, r, np.array([]), 0, 'row') assert_raises(ValueError, qr_insert, np.array([]), r, u, 0, 'col') assert_raises(ValueError, qr_insert, q, np.array([]), u, 0, 'col') assert_raises(ValueError, qr_insert, q, r, np.array([]), 0, 'col') def test_mismatched_shapes(self): a, q, r, u = self.generate('tall', which='row') assert_raises(ValueError, qr_insert, q, r[1:], u, 0, 'row') assert_raises(ValueError, qr_insert, q[:-2], r, u, 0, 'row') assert_raises(ValueError, qr_insert, q, r, u[1:], 0, 'row') assert_raises(ValueError, qr_insert, q, r[1:], u, 0, 'col') assert_raises(ValueError, qr_insert, q[:-2], r, u, 0, 'col') assert_raises(ValueError, qr_insert, q, r, u[1:], 0, 'col') def test_unsupported_dtypes(self): dts = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float16', 'longdouble', 'longcomplex', 'bool'] a, q0, r0, u0 = self.generate('sqr', which='row') for dtype in dts: q = q0.real.astype(dtype) with np.errstate(invalid="ignore"): r = r0.real.astype(dtype) u = u0.real.astype(dtype) assert_raises(ValueError, qr_insert, q, r0, u0, 0, 'row') assert_raises(ValueError, qr_insert, q, r0, u0, 0, 'col') assert_raises(ValueError, qr_insert, q0, r, u0, 0, 'row') assert_raises(ValueError, qr_insert, q0, r, u0, 0, 'col') assert_raises(ValueError, qr_insert, q0, r0, u, 0, 'row') assert_raises(ValueError, qr_insert, q0, r0, u, 0, 'col') def test_check_finite(self): a0, q0, r0, u0 = self.generate('sqr', which='row', p=3) q = q0.copy('F') q[1,1] = np.nan assert_raises(ValueError, qr_insert, q, r0, u0[:,0], 0, 'row') assert_raises(ValueError, qr_insert, q, r0, u0, 0, 'row') assert_raises(ValueError, qr_insert, q, r0, u0[:,0], 0, 'col') assert_raises(ValueError, qr_insert, q, r0, u0, 0, 'col') r = r0.copy('F') r[1,1] = np.nan assert_raises(ValueError, qr_insert, q0, r, u0[:,0], 0, 'row') assert_raises(ValueError, qr_insert, q0, r, u0, 0, 'row') assert_raises(ValueError, qr_insert, q0, r, u0[:,0], 0, 'col') assert_raises(ValueError, qr_insert, q0, r, u0, 0, 'col') u = u0.copy('F') u[0,0] = np.nan assert_raises(ValueError, qr_insert, q0, r0, u[:,0], 0, 'row') assert_raises(ValueError, qr_insert, q0, r0, u, 0, 'row') assert_raises(ValueError, qr_insert, q0, r0, u[:,0], 0, 'col') assert_raises(ValueError, qr_insert, q0, r0, u, 0, 'col') class TestQRinsert_f(BaseQRinsert): dtype = np.dtype('f') class TestQRinsert_F(BaseQRinsert): dtype = np.dtype('F') class TestQRinsert_d(BaseQRinsert): dtype = np.dtype('d') class TestQRinsert_D(BaseQRinsert): dtype = np.dtype('D') class BaseQRupdate(BaseQRdeltas): def generate(self, type, mode='full', p=1): a, q, r = super().generate(type, mode) # super call set the seed... if p == 1: u = np.random.random(q.shape[0]) v = np.random.random(r.shape[1]) else: u = np.random.random((q.shape[0], p)) v = np.random.random((r.shape[1], p)) if np.iscomplexobj(self.dtype.type(1)): b = np.random.random(u.shape) u = u + 1j * b c = np.random.random(v.shape) v = v + 1j * c u = u.astype(self.dtype) v = v.astype(self.dtype) return a, q, r, u, v def test_sqr_rank_1(self): a, q, r, u, v = self.generate('sqr') q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.outer(u, v.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_sqr_rank_p(self): # test ndim = 2, rank 1 updates here too for p in [1, 2, 3, 5]: a, q, r, u, v = self.generate('sqr', p=p) if p == 1: u = u.reshape(u.size, 1) v = v.reshape(v.size, 1) q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.dot(u, v.T.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_tall_rank_1(self): a, q, r, u, v = self.generate('tall') q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.outer(u, v.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_tall_rank_p(self): for p in [1, 2, 3, 5]: a, q, r, u, v = self.generate('tall', p=p) if p == 1: u = u.reshape(u.size, 1) v = v.reshape(v.size, 1) q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.dot(u, v.T.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_fat_rank_1(self): a, q, r, u, v = self.generate('fat') q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.outer(u, v.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_fat_rank_p(self): for p in [1, 2, 3, 5]: a, q, r, u, v = self.generate('fat', p=p) if p == 1: u = u.reshape(u.size, 1) v = v.reshape(v.size, 1) q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.dot(u, v.T.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_economic_rank_1(self): a, q, r, u, v = self.generate('tall', 'economic') q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.outer(u, v.conj()) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_economic_rank_p(self): for p in [1, 2, 3, 5]: a, q, r, u, v = self.generate('tall', 'economic', p) if p == 1: u = u.reshape(u.size, 1) v = v.reshape(v.size, 1) q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.dot(u, v.T.conj()) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_Mx1_rank_1(self): a, q, r, u, v = self.generate('Mx1') q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.outer(u, v.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_Mx1_rank_p(self): # when M or N == 1, only a rank 1 update is allowed. This isn't # fundamental limitation, but the code does not support it. a, q, r, u, v = self.generate('Mx1', p=1) u = u.reshape(u.size, 1) v = v.reshape(v.size, 1) q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.dot(u, v.T.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_Mx1_economic_rank_1(self): a, q, r, u, v = self.generate('Mx1', 'economic') q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.outer(u, v.conj()) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_Mx1_economic_rank_p(self): # when M or N == 1, only a rank 1 update is allowed. This isn't # fundamental limitation, but the code does not support it. a, q, r, u, v = self.generate('Mx1', 'economic', p=1) u = u.reshape(u.size, 1) v = v.reshape(v.size, 1) q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.dot(u, v.T.conj()) check_qr(q1, r1, a1, self.rtol, self.atol, False) def test_1xN_rank_1(self): a, q, r, u, v = self.generate('1xN') q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.outer(u, v.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1xN_rank_p(self): # when M or N == 1, only a rank 1 update is allowed. This isn't # fundamental limitation, but the code does not support it. a, q, r, u, v = self.generate('1xN', p=1) u = u.reshape(u.size, 1) v = v.reshape(v.size, 1) q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.dot(u, v.T.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1x1_rank_1(self): a, q, r, u, v = self.generate('1x1') q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.outer(u, v.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1x1_rank_p(self): # when M or N == 1, only a rank 1 update is allowed. This isn't # fundamental limitation, but the code does not support it. a, q, r, u, v = self.generate('1x1', p=1) u = u.reshape(u.size, 1) v = v.reshape(v.size, 1) q1, r1 = qr_update(q, r, u, v, False) a1 = a + np.dot(u, v.T.conj()) check_qr(q1, r1, a1, self.rtol, self.atol) def test_1x1_rank_1_scalar(self): a, q, r, u, v = self.generate('1x1') assert_raises(ValueError, qr_update, q[0, 0], r, u, v) assert_raises(ValueError, qr_update, q, r[0, 0], u, v) assert_raises(ValueError, qr_update, q, r, u[0], v) assert_raises(ValueError, qr_update, q, r, u, v[0]) def base_non_simple_strides(self, adjust_strides, mode, p, overwriteable): assert_sqr = False if mode == 'economic' else True for type in ['sqr', 'tall', 'fat']: a, q0, r0, u0, v0 = self.generate(type, mode, p) qs, rs, us, vs = adjust_strides((q0, r0, u0, v0)) if p == 1: aup = a + np.outer(u0, v0.conj()) else: aup = a + np.dot(u0, v0.T.conj()) # for each variable, q, r, u, v we try with it strided and # overwrite=False. Then we try with overwrite=True, and make # sure that if p == 1, r and v are still overwritten. # a strided q and u must always be copied. q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') v = v0.copy('C') q1, r1 = qr_update(qs, r, u, v, False) check_qr(q1, r1, aup, self.rtol, self.atol, assert_sqr) q1o, r1o = qr_update(qs, r, u, v, True) check_qr(q1o, r1o, aup, self.rtol, self.atol, assert_sqr) if overwriteable: assert_allclose(r1o, r, rtol=self.rtol, atol=self.atol) assert_allclose(v, v0.conj(), rtol=self.rtol, atol=self.atol) q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') v = v0.copy('C') q2, r2 = qr_update(q, rs, u, v, False) check_qr(q2, r2, aup, self.rtol, self.atol, assert_sqr) q2o, r2o = qr_update(q, rs, u, v, True) check_qr(q2o, r2o, aup, self.rtol, self.atol, assert_sqr) if overwriteable: assert_allclose(r2o, rs, rtol=self.rtol, atol=self.atol) assert_allclose(v, v0.conj(), rtol=self.rtol, atol=self.atol) q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') v = v0.copy('C') q3, r3 = qr_update(q, r, us, v, False) check_qr(q3, r3, aup, self.rtol, self.atol, assert_sqr) q3o, r3o = qr_update(q, r, us, v, True) check_qr(q3o, r3o, aup, self.rtol, self.atol, assert_sqr) if overwriteable: assert_allclose(r3o, r, rtol=self.rtol, atol=self.atol) assert_allclose(v, v0.conj(), rtol=self.rtol, atol=self.atol) q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') v = v0.copy('C') q4, r4 = qr_update(q, r, u, vs, False) check_qr(q4, r4, aup, self.rtol, self.atol, assert_sqr) q4o, r4o = qr_update(q, r, u, vs, True) check_qr(q4o, r4o, aup, self.rtol, self.atol, assert_sqr) if overwriteable: assert_allclose(r4o, r, rtol=self.rtol, atol=self.atol) assert_allclose(vs, v0.conj(), rtol=self.rtol, atol=self.atol) q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') v = v0.copy('C') # since some of these were consumed above qs, rs, us, vs = adjust_strides((q, r, u, v)) q5, r5 = qr_update(qs, rs, us, vs, False) check_qr(q5, r5, aup, self.rtol, self.atol, assert_sqr) q5o, r5o = qr_update(qs, rs, us, vs, True) check_qr(q5o, r5o, aup, self.rtol, self.atol, assert_sqr) if overwriteable: assert_allclose(r5o, rs, rtol=self.rtol, atol=self.atol) assert_allclose(vs, v0.conj(), rtol=self.rtol, atol=self.atol) def test_non_unit_strides_rank_1(self): self.base_non_simple_strides(make_strided, 'full', 1, True) def test_non_unit_strides_economic_rank_1(self): self.base_non_simple_strides(make_strided, 'economic', 1, True) def test_non_unit_strides_rank_p(self): self.base_non_simple_strides(make_strided, 'full', 3, False) def test_non_unit_strides_economic_rank_p(self): self.base_non_simple_strides(make_strided, 'economic', 3, False) def test_neg_strides_rank_1(self): self.base_non_simple_strides(negate_strides, 'full', 1, False) def test_neg_strides_economic_rank_1(self): self.base_non_simple_strides(negate_strides, 'economic', 1, False) def test_neg_strides_rank_p(self): self.base_non_simple_strides(negate_strides, 'full', 3, False) def test_neg_strides_economic_rank_p(self): self.base_non_simple_strides(negate_strides, 'economic', 3, False) def test_non_itemsize_strides_rank_1(self): self.base_non_simple_strides(nonitemsize_strides, 'full', 1, False) def test_non_itemsize_strides_economic_rank_1(self): self.base_non_simple_strides(nonitemsize_strides, 'economic', 1, False) def test_non_itemsize_strides_rank_p(self): self.base_non_simple_strides(nonitemsize_strides, 'full', 3, False) def test_non_itemsize_strides_economic_rank_p(self): self.base_non_simple_strides(nonitemsize_strides, 'economic', 3, False) def test_non_native_byte_order_rank_1(self): self.base_non_simple_strides(make_nonnative, 'full', 1, False) def test_non_native_byte_order_economic_rank_1(self): self.base_non_simple_strides(make_nonnative, 'economic', 1, False) def test_non_native_byte_order_rank_p(self): self.base_non_simple_strides(make_nonnative, 'full', 3, False) def test_non_native_byte_order_economic_rank_p(self): self.base_non_simple_strides(make_nonnative, 'economic', 3, False) def test_overwrite_qruv_rank_1(self): # Any positive strided q, r, u, and v can be overwritten for a rank 1 # update, only checking C and F contiguous. a, q0, r0, u0, v0 = self.generate('sqr') a1 = a + np.outer(u0, v0.conj()) q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') v = v0.copy('F') # don't overwrite q1, r1 = qr_update(q, r, u, v, False) check_qr(q1, r1, a1, self.rtol, self.atol) check_qr(q, r, a, self.rtol, self.atol) q2, r2 = qr_update(q, r, u, v, True) check_qr(q2, r2, a1, self.rtol, self.atol) # verify the overwriting, no good way to check u and v. assert_allclose(q2, q, rtol=self.rtol, atol=self.atol) assert_allclose(r2, r, rtol=self.rtol, atol=self.atol) q = q0.copy('C') r = r0.copy('C') u = u0.copy('C') v = v0.copy('C') q3, r3 = qr_update(q, r, u, v, True) check_qr(q3, r3, a1, self.rtol, self.atol) assert_allclose(q3, q, rtol=self.rtol, atol=self.atol) assert_allclose(r3, r, rtol=self.rtol, atol=self.atol) def test_overwrite_qruv_rank_1_economic(self): # updating economic decompositions can overwrite any contigous r, # and positively strided r and u. V is only ever read. # only checking C and F contiguous. a, q0, r0, u0, v0 = self.generate('tall', 'economic') a1 = a + np.outer(u0, v0.conj()) q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') v = v0.copy('F') # don't overwrite q1, r1 = qr_update(q, r, u, v, False) check_qr(q1, r1, a1, self.rtol, self.atol, False) check_qr(q, r, a, self.rtol, self.atol, False) q2, r2 = qr_update(q, r, u, v, True) check_qr(q2, r2, a1, self.rtol, self.atol, False) # verify the overwriting, no good way to check u and v. assert_allclose(q2, q, rtol=self.rtol, atol=self.atol) assert_allclose(r2, r, rtol=self.rtol, atol=self.atol) q = q0.copy('C') r = r0.copy('C') u = u0.copy('C') v = v0.copy('C') q3, r3 = qr_update(q, r, u, v, True) check_qr(q3, r3, a1, self.rtol, self.atol, False) assert_allclose(q3, q, rtol=self.rtol, atol=self.atol) assert_allclose(r3, r, rtol=self.rtol, atol=self.atol) def test_overwrite_qruv_rank_p(self): # for rank p updates, q r must be F contiguous, v must be C (v.T --> F) # and u can be C or F, but is only overwritten if Q is C and complex a, q0, r0, u0, v0 = self.generate('sqr', p=3) a1 = a + np.dot(u0, v0.T.conj()) q = q0.copy('F') r = r0.copy('F') u = u0.copy('F') v = v0.copy('C') # don't overwrite q1, r1 = qr_update(q, r, u, v, False) check_qr(q1, r1, a1, self.rtol, self.atol) check_qr(q, r, a, self.rtol, self.atol) q2, r2 = qr_update(q, r, u, v, True) check_qr(q2, r2, a1, self.rtol, self.atol) # verify the overwriting, no good way to check u and v. assert_allclose(q2, q, rtol=self.rtol, atol=self.atol) assert_allclose(r2, r, rtol=self.rtol, atol=self.atol) def test_empty_inputs(self): a, q, r, u, v = self.generate('tall') assert_raises(ValueError, qr_update, np.array([]), r, u, v) assert_raises(ValueError, qr_update, q, np.array([]), u, v) assert_raises(ValueError, qr_update, q, r, np.array([]), v) assert_raises(ValueError, qr_update, q, r, u, np.array([])) def test_mismatched_shapes(self): a, q, r, u, v = self.generate('tall') assert_raises(ValueError, qr_update, q, r[1:], u, v) assert_raises(ValueError, qr_update, q[:-2], r, u, v) assert_raises(ValueError, qr_update, q, r, u[1:], v) assert_raises(ValueError, qr_update, q, r, u, v[1:]) def test_unsupported_dtypes(self): dts = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float16', 'longdouble', 'longcomplex', 'bool'] a, q0, r0, u0, v0 = self.generate('tall') for dtype in dts: q = q0.real.astype(dtype) with np.errstate(invalid="ignore"): r = r0.real.astype(dtype) u = u0.real.astype(dtype) v = v0.real.astype(dtype) assert_raises(ValueError, qr_update, q, r0, u0, v0) assert_raises(ValueError, qr_update, q0, r, u0, v0) assert_raises(ValueError, qr_update, q0, r0, u, v0) assert_raises(ValueError, qr_update, q0, r0, u0, v) def test_integer_input(self): q = np.arange(16).reshape(4, 4) r = q.copy() # doesn't matter u = q[:, 0].copy() v = r[0, :].copy() assert_raises(ValueError, qr_update, q, r, u, v) def test_check_finite(self): a0, q0, r0, u0, v0 = self.generate('tall', p=3) q = q0.copy('F') q[1,1] = np.nan assert_raises(ValueError, qr_update, q, r0, u0[:,0], v0[:,0]) assert_raises(ValueError, qr_update, q, r0, u0, v0) r = r0.copy('F') r[1,1] = np.nan assert_raises(ValueError, qr_update, q0, r, u0[:,0], v0[:,0]) assert_raises(ValueError, qr_update, q0, r, u0, v0) u = u0.copy('F') u[0,0] = np.nan assert_raises(ValueError, qr_update, q0, r0, u[:,0], v0[:,0]) assert_raises(ValueError, qr_update, q0, r0, u, v0) v = v0.copy('F') v[0,0] = np.nan assert_raises(ValueError, qr_update, q0, r0, u[:,0], v[:,0]) assert_raises(ValueError, qr_update, q0, r0, u, v) def test_economic_check_finite(self): a0, q0, r0, u0, v0 = self.generate('tall', mode='economic', p=3) q = q0.copy('F') q[1,1] = np.nan assert_raises(ValueError, qr_update, q, r0, u0[:,0], v0[:,0]) assert_raises(ValueError, qr_update, q, r0, u0, v0) r = r0.copy('F') r[1,1] = np.nan assert_raises(ValueError, qr_update, q0, r, u0[:,0], v0[:,0]) assert_raises(ValueError, qr_update, q0, r, u0, v0) u = u0.copy('F') u[0,0] = np.nan assert_raises(ValueError, qr_update, q0, r0, u[:,0], v0[:,0]) assert_raises(ValueError, qr_update, q0, r0, u, v0) v = v0.copy('F') v[0,0] = np.nan assert_raises(ValueError, qr_update, q0, r0, u[:,0], v[:,0]) assert_raises(ValueError, qr_update, q0, r0, u, v) def test_u_exactly_in_span_q(self): q = np.array([[0, 0], [0, 0], [1, 0], [0, 1]], self.dtype) r = np.array([[1, 0], [0, 1]], self.dtype) u = np.array([0, 0, 0, -1], self.dtype) v = np.array([1, 2], self.dtype) q1, r1 = qr_update(q, r, u, v) a1 = np.dot(q, r) + np.outer(u, v.conj()) check_qr(q1, r1, a1, self.rtol, self.atol, False) class TestQRupdate_f(BaseQRupdate): dtype = np.dtype('f') class TestQRupdate_F(BaseQRupdate): dtype = np.dtype('F') class TestQRupdate_d(BaseQRupdate): dtype = np.dtype('d') class TestQRupdate_D(BaseQRupdate): dtype = np.dtype('D') def test_form_qTu(): # We want to ensure that all of the code paths through this function are # tested. Most of them should be hit with the rest of test suite, but # explicit tests make clear precisely what is being tested. # # This function expects that Q is either C or F contiguous and square. # Economic mode decompositions (Q is (M, N), M != N) do not go through this # function. U may have any positive strides. # # Some of these test are duplicates, since contiguous 1d arrays are both C # and F. q_order = ['F', 'C'] q_shape = [(8, 8), ] u_order = ['F', 'C', 'A'] # here A means is not F not C u_shape = [1, 3] dtype = ['f', 'd', 'F', 'D'] for qo, qs, uo, us, d in \ itertools.product(q_order, q_shape, u_order, u_shape, dtype): if us == 1: check_form_qTu(qo, qs, uo, us, 1, d) check_form_qTu(qo, qs, uo, us, 2, d) else: check_form_qTu(qo, qs, uo, us, 2, d) def check_form_qTu(q_order, q_shape, u_order, u_shape, u_ndim, dtype): np.random.seed(47) if u_shape == 1 and u_ndim == 1: u_shape = (q_shape[0],) else: u_shape = (q_shape[0], u_shape) dtype = np.dtype(dtype) if dtype.char in 'fd': q = np.random.random(q_shape) u = np.random.random(u_shape) elif dtype.char in 'FD': q = np.random.random(q_shape) + 1j*np.random.random(q_shape) u = np.random.random(u_shape) + 1j*np.random.random(u_shape) else: ValueError("form_qTu doesn't support this dtype") q = np.require(q, dtype, q_order) if u_order != 'A': u = np.require(u, dtype, u_order) else: u, = make_strided((u.astype(dtype),)) rtol = 10.0 ** -(np.finfo(dtype).precision-2) atol = 2*np.finfo(dtype).eps expected = np.dot(q.T.conj(), u) res = _decomp_update._form_qTu(q, u) assert_allclose(res, expected, rtol=rtol, atol=atol)