def test_posterior_mean_CG_equivalency(self): """ The probabilistic linear solver(s) should recover CG iterates as a posterior mean for specific covariances. """ # Linear system A, b = self.poisson_linear_system # Callback function to return CG iterates cg_iterates = [] def callback_iterates_CG(xk): cg_iterates.append( np.eye(np.shape(A)[0]) @ xk ) # identity hack to actually save different iterations # Solve linear system # Initial guess as chosen by PLS: x0 = Ainv.mean @ b x0 = b # Conjugate gradient method xhat_cg, info_cg = scipy.sparse.linalg.cg( A=A, b=b, x0=x0, tol=10 ** -6, callback=callback_iterates_CG ) cg_iters_arr = np.array([x0] + cg_iterates) # Matrix priors (encoding weak symmetric posterior correspondence) Ainv0 = rvs.Normal( mean=linops.Identity(A.shape[1]), cov=linops.SymmetricKronecker(A=linops.Identity(A.shape[1])), ) A0 = rvs.Normal( mean=linops.Identity(A.shape[1]), cov=linops.SymmetricKronecker(A) ) for kwargs in [{"assume_A": "sympos", "rtol": 10 ** -6}]: with self.subTest(): # Define callback function to obtain search directions pls_iterates = [] # pylint: disable=cell-var-from-loop def callback_iterates_PLS( xk, Ak, Ainvk, sk, yk, alphak, resid, **kwargs ): pls_iterates.append(xk.mean) # Probabilistic linear solver xhat_pls, _, _, info_pls = linalg.problinsolve( A=A, b=b, Ainv0=Ainv0, A0=A0, callback=callback_iterates_PLS, **kwargs ) pls_iters_arr = np.array([x0] + pls_iterates) self.assertAllClose(xhat_pls.mean, xhat_cg, rtol=10 ** -12) self.assertAllClose(pls_iters_arr, cg_iters_arr, rtol=10 ** -12)
def test_symmkronecker_transpose(self): """Kronecker product transpose property: (A (x) B)^T = A^T (x) B^T.""" for A, B in self.symmkronecker_matrices: with self.subTest(): W = linops.SymmetricKronecker(A=A, B=B) V = linops.SymmetricKronecker(A=A.T, B=B.T) self.assertAllClose(W.T.todense(), V.todense())
def test_symmkronecker_commutation(self): """Symmetric Kronecker products fulfill A (x)_s B = B (x)_s A""" for A, B in self.symmkronecker_matrices: with self.subTest(): W = linops.SymmetricKronecker(A=A, B=B) V = linops.SymmetricKronecker(A=B, B=A) self.assertAllClose(W.todense(), V.todense())
def get_randvar(rv_name): """Return a random variable for a given distribution name.""" # Distribution Means and Covariances mean_0d = np.random.rand() mean_1d = np.random.rand(5) mean_2d_mat = SPD_MATRIX_5x5 mean_2d_linop = linops.MatrixMult(SPD_MATRIX_5x5) cov_0d = np.random.rand() + 10**-12 cov_1d = SPD_MATRIX_5x5 cov_2d_kron = linops.Kronecker(A=SPD_MATRIX_5x5, B=SPD_MATRIX_5x5) cov_2d_symkron = linops.SymmetricKronecker(A=SPD_MATRIX_5x5) if rv_name == "univar_normal": randvar = rvs.Normal(mean=mean_0d, cov=cov_0d) elif rv_name == "multivar_normal": randvar = rvs.Normal(mean=mean_1d, cov=cov_1d) elif rv_name == "matrixvar_normal": randvar = rvs.Normal(mean=mean_2d_mat, cov=cov_2d_kron) elif rv_name == "symmatrixvar_normal": randvar = rvs.Normal(mean=mean_2d_mat, cov=cov_2d_symkron) elif rv_name == "operatorvar_normal": randvar = rvs.Normal(mean=mean_2d_linop, cov=cov_2d_symkron) return randvar
def test_matrixprior(self): """Solve random linear system with a matrix-based linear solver.""" np.random.seed(1) # Linear system n = 10 A = np.random.rand(n, n) A = A.dot(A.T) + n * np.eye(n) # Symmetrize and make diagonally dominant x_true = np.random.normal(size=(n,)) b = A @ x_true # Prior distributions on A covA = linops.SymmetricKronecker(A=np.eye(n)) Ainv0 = rvs.Normal(mean=np.eye(n), cov=covA) for matblinsolve in self.matblinsolvers: with self.subTest(): x, Ahat, Ainvhat, info = matblinsolve(A=A, Ainv0=Ainv0, b=b) self.assertAllClose( x.mean, x_true, rtol=1e-6, atol=1e-6, msg="Solution for matrixvariate prior does not match true solution.", )
def setUp(self): """Scalars, arrays, linear operators and random variables for tests.""" # Seed np.random.seed(42) # Random variable instantiation self.scalars = [0, int(1), 0.1, np.nan, np.inf] self.arrays = [np.empty(2), np.zeros(4), np.array([]), np.array([1, 2])] # Random variable arithmetic self.arrays2d = [ np.empty(2), np.zeros(2), np.array([np.inf, 1]), np.array([1, -2.5]), ] self.matrices2d = [np.array([[1, 2], [3, 2]]), np.array([[0, 0], [1.0, -4.3]])] self.linops2d = [linops.MatrixMult(A=np.array([[1, 2], [4, 5]]))] self.randvars2d = [ rvs.Normal(mean=np.array([1, 2]), cov=np.array([[2, 0], [0, 5]])) ] self.randvars2x2 = [ rvs.Normal( mean=np.array([[-2, 0.3], [0, 1]]), cov=linops.SymmetricKronecker(A=np.eye(2), B=np.ones((2, 2))), ), ] self.scipyrvs = [ scipy.stats.bernoulli(0.75), scipy.stats.norm(4, 2.4), scipy.stats.multivariate_normal(np.random.randn(10), np.eye(10)), scipy.stats.gamma(0.74), scipy.stats.dirichlet(alpha=np.array([0.1, 0.1, 0.2, 0.3])), ]
def setUp(self): """Resources for tests.""" # Random Seed np.random.seed(42) # Scalars, arrays and operators self.scalars = [0, int(1), 0.1, -4.2, np.nan, np.inf] self.arrays = [ np.random.normal(size=[5, 4]), np.array([[3, 4], [1, 5]]) ] def mv(v): return np.array([2 * v[0], v[0] + 3 * v[1]]) self.mv = mv self.ops = [ linops.MatrixMult(np.array([[-1.5, 3], [0, -230]])), linops.LinearOperator(shape=(2, 2), matvec=mv), linops.Identity(shape=4), linops.Kronecker( A=linops.MatrixMult(np.array([[2, -3.5], [12, 6.5]])), B=linops.Identity(shape=3), ), linops.SymmetricKronecker( A=linops.MatrixMult(np.array([[1, -2], [-2.2, 5]])), B=linops.MatrixMult(np.array([[1, -3], [0, -0.5]])), ), ]
def test_symmkronecker_todense_symmetric(self): """Dense matrix from symmetric Kronecker product of two symmetric matrices must be symmetric.""" C = np.array([[5, 1], [1, 10]]) D = np.array([[-2, 0.1], [0.1, 8]]) Ws = linops.SymmetricKronecker(A=C, B=C) Ws_dense = Ws.todense() self.assertArrayEqual( Ws_dense, Ws_dense.T, msg= "Symmetric Kronecker product of symmetric matrices is not symmetric.", )
def _symmetric_kronecker_identical_factors_cov_cholesky( self, ) -> linops.SymmetricKronecker: assert isinstance(self._cov, linops.SymmetricKronecker) and self._cov._ABequal A = self._cov.A.todense() return linops.SymmetricKronecker( A=scipy.linalg.cholesky( A + COV_CHOLESKY_DAMPING * np.eye(A.shape[0], dtype=self.dtype), lower=True, ), dtype=self.dtype, )