Exemple #1
0
    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]])),
            ),
        ]
Exemple #2
0
    def test_rv_linop_kroneckercov(self):
        """Create a rv with a normal distribution with linear operator mean and Kronecker product kernels."""
        def mv(v):
            return np.array([2 * v[0], 3 * v[1]])

        A = linops.LinearOperator(shape=(2, 2), matvec=mv)
        V = linops.Kronecker(A, A)
        rvs.Normal(mean=A, cov=V)
Exemple #3
0
    def test_linop_construction(self):
        """Create linear operators via various construction methods."""

        # Custom linear operator
        linops.LinearOperator(shape=(2, 2), matvec=self.mv)

        # Scipy linear operator
        scipy_linop = scipy.sparse.linalg.LinearOperator(shape=(2, 2), matvec=self.mv)
        linops.aslinop(scipy_linop)
    def test_rv_linop_kroneckercov(self):
        """Create a rv with a normal distribution with linear operator mean and
        Kronecker product kernels."""

        @linops.LinearOperator.broadcast_matvec
        def _matmul(v):
            return np.array([2 * v[0], 3 * v[1]])

        A = linops.LinearOperator(shape=(2, 2), dtype=np.double, matmul=_matmul)
        V = linops.Kronecker(A, A)
        randvars.Normal(mean=A, cov=V)
Exemple #5
0
    def _covariance_update(self, u, Ws):
        """Linear operator implementing the symmetric rank 2 kernels update (-= Ws
        u^T)."""
        def _matmul(x):
            return Ws @ (u.T @ x)

        return linops.LinearOperator(
            shape=(self.n, self.n),
            dtype=np.result_type(u.dtype, Ws.dtype),
            matmul=_matmul,
        )
Exemple #6
0
    def _mean_update(self, u, v):
        """Linear operator implementing the symmetric rank 2 mean update (+= uv' +
        vu')."""
        def _matmul(x):
            return u @ (v.T @ x) + v @ (u.T @ x)

        return linops.LinearOperator(
            shape=(self.n, self.n),
            dtype=np.result_type(u.dtype, v.dtype),
            matmul=_matmul,
        )
Exemple #7
0
    def _get_calibration_covariance_update_terms(self, phi=None, psi=None):
        """For the calibration covariance class set the calibration update terms of the
        covariance in the null spaces of span(S) and span(Y) based on the degrees of
        freedom."""
        # Search directions and observations as arrays
        S = np.hstack(self.search_dir_list)
        Y = np.hstack(self.obs_list)

        def get_null_space_map(V, unc_scale):
            """Returns a function mapping to the null space of span(V), scaling with a
            single degree of freedom and mapping back."""
            def null_space_proj(x):
                try:
                    VVinvVx = np.linalg.solve(V.T @ V, V.T @ x)
                    return x - V @ VVinvVx
                except np.linalg.LinAlgError:
                    return np.zeros_like(x)

            # For a scalar uncertainty scale projecting to the null space twice is
            # equivalent to projecting once
            return lambda y: unc_scale * null_space_proj(y)

        # Compute calibration term in the A view as a linear operator with scaling from
        # degrees of freedom
        calibration_term_A = linops.LinearOperator(
            shape=(self.n, self.n),
            dtype=S.dtype,
            matmul=linops.LinearOperator.broadcast_matvec(
                get_null_space_map(V=S, unc_scale=phi)),
        )

        # Compute calibration term in the Ainv view as a linear operator with scaling
        # from degrees of freedom
        calibration_term_Ainv = linops.LinearOperator(
            shape=(self.n, self.n),
            dtype=S.dtype,
            matmul=linops.LinearOperator.broadcast_matvec(
                get_null_space_map(V=Y, unc_scale=psi)),
        )

        return calibration_term_A, calibration_term_Ainv
Exemple #8
0
    def _get_output_randvars(self, Y_list, sy_list, phi=None, psi=None):
        """Return output random variables x, A, Ainv from their means and
        covariances."""

        if self.iter_ > 0:
            # Observations and inner products in A-space between actions
            Y = np.hstack(Y_list)
            sy = np.vstack(sy_list).ravel()

            # Posterior covariance factors
            if self.is_calib_covclass and (not phi is None) and (
                    not psi is None):
                # Ensure prior covariance class only acts in span(S) like A
                @linops.LinearOperator.broadcast_matvec
                def _matmul(x):
                    # First term of calibration covariance class: AS(S'AS)^{-1}S'A
                    return (Y * sy**-1) @ (Y.T @ x.ravel())

                _A_covfactor0 = linops.LinearOperator(
                    shape=(self.n, self.n),
                    dtype=np.result_type(Y, sy),
                    matmul=_matmul,
                )

                @linops.LinearOperator.broadcast_matvec
                def _matmul(x):
                    # Term in covariance class: A_0^{-1}Y(Y'A_0^{-1}Y)^{-1}Y'A_0^{-1}
                    # TODO: for efficiency ensure that we dont have to compute
                    # (Y.T Y)^{-1} two times! For a scalar mean this is the same as in
                    # the null space projection
                    YAinv0Y_inv_YAinv0x = np.linalg.solve(
                        Y.T @ (self.Ainv_mean0 @ Y),
                        Y.T @ (self.Ainv_mean0 @ x))
                    return self.Ainv_mean0 @ (Y @ YAinv0Y_inv_YAinv0x)

                _Ainv_covfactor0 = linops.LinearOperator(
                    shape=(self.n, self.n),
                    dtype=np.result_type(Y, self.Ainv_mean0),
                    matmul=_matmul,
                )

                # Set degrees of freedom based on uncertainty calibration in unexplored
                # space
                (
                    calibration_term_A,
                    calibration_term_Ainv,
                ) = self._get_calibration_covariance_update_terms(phi=phi,
                                                                  psi=psi)

                _A_covfactor = (_A_covfactor0 - self._A_covfactor_update_term +
                                calibration_term_A)
                _Ainv_covfactor = (_Ainv_covfactor0 -
                                   self._Ainv_covfactor_update_term +
                                   calibration_term_Ainv)
            else:
                # No calibration
                _A_covfactor = self.A_covfactor
                _Ainv_covfactor = self.Ainv_covfactor
        else:
            # Converged before making any observations
            _A_covfactor = self.A_covfactor0
            _Ainv_covfactor = self.Ainv_covfactor0

        # Create output random variables
        A = randvars.Normal(mean=self.A_mean,
                            cov=linops.SymmetricKronecker(A=_A_covfactor))

        Ainv = randvars.Normal(
            mean=self.Ainv_mean,
            cov=linops.SymmetricKronecker(A=_Ainv_covfactor),
        )
        # Induced distribution on x via Ainv
        # Exp(x) = Ainv b, Cov(x) = 1/2 (W b'Wb + Wbb'W)
        Wb = _Ainv_covfactor @ self.b
        bWb = np.squeeze(Wb.T @ self.b)

        def _matmul(x):
            return 0.5 * (bWb * _Ainv_covfactor @ x + Wb @ (Wb.T @ x))

        cov_op = linops.LinearOperator(
            shape=(self.n, self.n),
            dtype=np.result_type(Wb.dtype, bWb.dtype),
            matmul=_matmul,
        )

        x = randvars.Normal(mean=self.x_mean.ravel(), cov=cov_op)

        # Compute trace of solution covariance: tr(Cov(x))
        self.trace_sol_cov = np.real_if_close(
            self._compute_trace_solution_covariance(bWb=bWb, Wb=Wb)).item()

        return x, A, Ainv
Exemple #9
0
    def _construct_symmetric_matrix_prior_means(self, A, x0, b):
        """Create matrix prior means from an initial guess for the solution of the
        linear system.

        Constructs a matrix-variate prior mean for H from ``x0`` and ``b`` such that
        :math:`H_0b = x_0`, :math:`H_0` symmetric positive definite and
        :math:`A_0 = H_0^{-1}`.

        Parameters
        ----------
        A : array-like or LinearOperator, shape=(n,n)
            System matrix assumed to be square.
        x0 : array-like, shape=(n,) or (n, nrhs)
            Optional. Guess for the solution of the linear system.
        b : array_like, shape=(n,) or (n, nrhs)
            Right-hand side vector or matrix in :math:`A x = b`.

        Returns
        -------
        A0_mean : linops.LinearOperator
            Mean of the matrix-variate prior distribution on the system matrix
            :math:`A`.
        Ainv0_mean : linops.LinearOperator
            Mean of the matrix-variate prior distribution on the inverse of the system
            matrix :math:`H = A^{-1}`.
        """
        # Check inner product between x0 and b; if negative or zero, choose better
        # initialization
        bx0 = np.squeeze(b.T @ x0)
        bb = np.linalg.norm(b)**2
        if bx0 < 0:
            x0 = -x0
            bx0 = -bx0
            print("Better initialization found, setting x0 = - x0.")
        elif bx0 == 0:
            if np.all(b == np.zeros_like(b)):
                print(
                    "Right-hand-side is zero. Initializing with solution x0 = 0."
                )
                x0 = b
            else:
                print(
                    "Better initialization found, setting x0 = (b'b/b'Ab) * b."
                )
                bAb = np.squeeze(b.T @ (A @ b))
                x0 = bb / bAb * b
                bx0 = bb**2 / bAb

        # Construct prior mean of A and H
        alpha = 0.5 * bx0 / bb

        def _matmul(M):
            return (x0 - alpha * b) @ (x0 - alpha * b).T @ M

        Ainv0_mean = linops.Scaling(
            alpha, shape=(self.n, self.n)) + 2 / bx0 * linops.LinearOperator(
                shape=(self.n, self.n),
                dtype=np.result_type(x0.dtype, alpha.dtype, b.dtype),
                matmul=_matmul,
            )
        A0_mean = linops.Scaling(
            1 / alpha, shape=(self.n, self.n)) - 1 / (alpha * np.squeeze(
                (x0 - alpha * b).T @ x0)) * linops.LinearOperator(
                    shape=(self.n, self.n),
                    dtype=np.result_type(x0.dtype, alpha.dtype, b.dtype),
                    matmul=_matmul,
                )
        return A0_mean, Ainv0_mean