Source code for cofi.utils._reg_model_cov

from numbers import Number
from typing import Union, Tuple
import numpy as np

from ._reg_base import BaseRegularization
from .._exceptions import DimensionMismatchError


[docs] class ModelCovariance(BaseRegularization): r"""CoFI's utility abstract class to calculate model prior distribution See :class:`GaussianPrior` for a concrete subclass example. """
[docs] class GaussianPrior(ModelCovariance): r"""CoFI's utility class to calculate the Gaussian prior, given the inverse of model covariance matrix and the mean model :math:`GaussianPrior(C_m^{-1}, \mu) = (m-\mu)^TC_m^{-1}(m-\mu)` With gradient of: :math:`2\times C_m^{-1} (m-\mu)` And hessian of: :math:`2\times C_m^{-1}` Optionally, the inverse model covariance matrix can be constructed given the correlation lengths and sigma. Parameters ---------- model_covariance_inv: np.ndarray or (tuple, float) the inverse model covariance matrix, either provided by users or specified so can be generated, corresponding to :math:`C_m^{-1}` in the formula above. When this is a tuple (i.e. CoFI will construct the matrix for you), it should be in the form of `(corr_lengths, sigma)` mean_model: np.ndarray the prior model, corresponding to :math:`\mu` in the formula above Raises ------ TypeError if arguments aren't in accepted types as described above in "Parameters" section ValueError if shape of model_covariance_inv and mean_model doesn't match, or if shape of corr_lengths and mean_model doesn't match Examples -------- Generate a Gaussian Prior term for models of size (4,4), with correlation lengths of (2,2), and sigma of 0.5: >>> from cofi.utils import GaussianPrior >>> import numpy as np >>> my_prior_model = np.array([[1,2],[3,4]]) >>> my_reg = GaussianPrior(model_covariance_inv=((2,2), 0.5), mean_model=my_prior_model) >>> my_reg(np.array([[0,2],[1,0]])) 101.53804950804782 To use together with :class:`cofi.BaseProblem`: >>> from cofi import BaseProblem >>> my_problem = BaseProblem() >>> my_problem.set_regularization(my_reg) """ def __init__( self, model_covariance_inv: Union[np.ndarray, Tuple[Tuple, float]], mean_model: np.ndarray, ): self._mu = np.ravel(mean_model) self._model_shape = mean_model.shape self._prepare_covariance_matrix_inv(model_covariance_inv, mean_model)
[docs] def reg(self, model: np.ndarray) -> Number: flat_m = self._validate_model(model) diff_m = flat_m - self._mu return diff_m.T @ self._Cminv @ diff_m
[docs] def gradient(self, model: np.ndarray) -> np.ndarray: flat_m = self._validate_model(model) return 2 * self._Cminv @ (flat_m - self._mu)
[docs] def hessian(self, model: np.ndarray) -> np.ndarray: return 2 * self._Cminv
@property def model_shape(self) -> tuple: return self._model_shape @property def gaussian_model_covariance_inv(self) -> np.ndarray: return self._Cminv def _prepare_covariance_matrix_inv(self, model_covariance_inv, mean_model): if isinstance(model_covariance_inv, np.ndarray): mu = self._mu Cminv = model_covariance_inv if Cminv.shape != (mu.shape[0], mu.shape[0]): raise ValueError( f"({(mu.shape[0], mu.shape[0])}) expected for the shape of " f"model_covariance_inv but got matrix of shape {Cminv.shape}" ) self._Cminv = model_covariance_inv elif isinstance(model_covariance_inv, (tuple, list)): self._Cminv = self._generate_covariance_matrix_inv( mean_model.shape, model_covariance_inv[0], model_covariance_inv[1], ) else: raise TypeError( "numpy.ndarray or (tuple, float) expected for `model_covariance_inv` " f"but got {model_covariance_inv} of type {type(model_covariance_inv)}" ) def _generate_covariance_matrix_inv( self, model_shape: tuple, corr_lengths: tuple, sigma: float ): # ensure model_shape and corr_lengths have the same length if len(model_shape) != len(corr_lengths): raise ValueError( "`model_shape` and `corr_lengths` should have the same lengths, " f"but got {len(model_shape)} and {len(corr_lengths)}" ) # generate grid of points for each dimension grids = np.meshgrid(*[np.arange(dim) for dim in model_shape], indexing="ij") # calculate distances between points for each pair of dimensions d_squared = sum([ (grid.ravel()[None, :] - grid.ravel()[:, None]) ** 2 / corr_length**2 for grid, corr_length in zip(grids, corr_lengths) ]) # construct correlation matrix Cp = np.exp(-np.sqrt(d_squared)) # construct variance matrix Sc = np.zeros((np.prod(model_shape), np.prod(model_shape))) np.fill_diagonal(Sc, sigma) # calculate covariance matrix covariance_matrix = Sc @ Cp @ Sc # calculate inverse covariance matrix covariance_matrix_inv = np.linalg.inv(covariance_matrix) return covariance_matrix_inv def _validate_model(self, model): flat_m = np.ravel(model) if flat_m.size != self.model_size: raise DimensionMismatchError( entered_name="model", entered_dimension=model.shape, expected_source="model_size", expected_dimension=self.model_size, ) return flat_m