def eigs(self): """ Eigendecomposition of volume covariance matrix of simulation :return: A 2-tuple: eigs_true: The eigenvectors of the volume covariance matrix in the form of an L-by-L-by-L-by-(C-1) array, where C is the number of distinct states in the simulation lambdas_true: The eigenvalues of the covariance matrix in the form of a (C-1)-by-(C-1) diagonal matrix. """ C = self.C vols_c = self.vols - np.expand_dims(self.mean_true(), 3) p = np.ones(C) / C vols_c = vol_to_vec(vols_c) Q, R = qr(vols_c, mode='economic') # Rank is at most C-1, so remove last vector Q = Q[:, :-1] R = R[:-1, :] w, v = eigh(make_symmat(R @ np.diag(p) @ R.T)) eigs_true = vec_to_vol(Q @ v) # Arrange in descending order (flip column order in eigenvector matrix) w = w[::-1] eigs_true = np.flip(eigs_true, axis=-1) return eigs_true, np.diag(w)
def expand_t(self, v): """ Expand array in dual basis This is a similar function to `evaluate` but with more accuracy by using the cg optimizing of linear equation, Ax=b. If `v` is a matrix of size `basis.ct`-by-..., `B` is the change-of-basis matrix of this basis, and `x` is a matrix of size `self.sz`-by-..., the function calculates x = (B * B')^(-1) * B * v, where the rows of `B` and columns of `x` are read as vectorized arrays. :param v: An array whose first dimension is to be expanded in this basis's dual. This dimension must be equal to `self.count`. :return: The coefficients of `v` expanded in the dual of `basis`. If more than one vector is supplied in `v`, the higher dimensions of the return value correspond to second and higher dimensions of `v`. .. seealso:: expand """ ensure(v.shape[0] == self.count, f'First dimension of v must be {self.count}') v, sz_roll = unroll_dim(v, 2) b = vol_to_vec(self.evaluate(v)) operator = LinearOperator( shape=(self.nres ** 3, self.nres ** 3), matvec=lambda x: vol_to_vec(self.evaluate(self.evaluate_t(vec_to_vol(x)))) ) # TODO: (from MATLAB implementation) - Check that this tolerance make sense for multiple columns in v tol = 10 * np.finfo(v.dtype).eps logger.info('Expanding array in dual basis') v, info = cg(operator, b, tol=tol) v = v[..., np.newaxis] if info != 0: raise RuntimeError('Unable to converge!') v = roll_dim(v, sz_roll) x = vec_to_vol(v) return x
def expand_t(self, v): ensure(v.shape[0] == self.basis_count, f'First dimension of v must be {self.basis_count}') v, sz_roll = unroll_dim(v, 2) b = vol_to_vec(self.evaluate(v)) operator = LinearOperator( shape=(self.N**3, self.N**3), matvec=lambda x: vol_to_vec( self.evaluate(self.evaluate_t(vec_to_vol(x))))) # TODO: (from MATLAB implementation) - Check that this tolerance make sense for multiple columns in v tol = 10 * np.finfo(v.dtype).eps logger.info('Expanding array in dual basis') v, info = cg(operator, b, tol=tol) if info != 0: raise RuntimeError('Unable to converge!') v = roll_dim(v, sz_roll) x = vec_to_vol(v) return x
def toeplitz(self, L=None): """ Compute the 3D Toeplitz matrix corresponding to this Fourier Kernel :param L: The size of the volumes to be convolved (default M/2, where the dimensions of this Fourier Kernel are MxMxM :return: An six-dimensional Toeplitz matrix of size L describing the convolution of a volume with this kernel """ if L is None: L = int(self.M / 2) A = np.eye(L**3, dtype=self.as_type) for i in range(L**3): A[:, i] = vol_to_vec(self.convolve_volume(vec_to_vol(A[:, i]))) A = vecmat_to_volmat(A) return A
def vol_coords(self, mean_vol=None, eig_vols=None): """ Coordinates of simulation volumes in a given basis :param mean_vol: A mean volume in the form of an L-by-L-by-L array (default `mean_true`). :param eig_vols: A set of eigenvolumes in an L-by-L-by-L-by-K array (default `eigs`). :return: """ if mean_vol is None: mean_vol = self.mean_true() if eig_vols is None: eig_vols = self.eigs()[0] vols = self.vols - np.expand_dims(mean_vol, 3) coords = vol_to_vec(eig_vols).T @ vol_to_vec(vols) res = vols - vec_to_vol(vol_to_vec(eig_vols) @ coords) res_norms = np.diag(anorm(res, (0, 1, 2))) res_inners = vol_to_vec(mean_vol).T @ vol_to_vec(res) return coords.squeeze(), res_norms, res_inners