Source code for cuqi.distribution._cmrf

import numpy as np
import warnings
from cuqi.geometry import _DefaultGeometry1D, Image2D, _get_identity_geometries
from cuqi.distribution import Distribution
from cuqi.operator import FirstOrderFiniteDifference
from cuqi.utilities import force_ndarray


[docs] class CMRF(Distribution): """Cauchy distribution on the difference between neighboring nodes. For 1D the Cauchy difference distribution assumes that .. math:: x_i-x_{i-1} \sim \mathrm{Cauchy}(0, \gamma), where :math:`\gamma` is the scale parameter. For 2D the differences are defined in both horizontal and vertical directions. It is possible to define boundary conditions using the `bc_type` parameter. The location parameter is a shift of the :math:`\mathbf{x}`. Parameters ---------- location : scalar or ndarray The location parameter of the distribution. scale : scalar The scale parameter of the distribution. bc_type : string The boundary conditions of the difference operator. Example ------- .. code-block:: python import cuqi import numpy as np prior = cuqi.distribution.CMRF(location=np.zeros(128), scale=0.1) """
[docs] def __init__(self, location=None, scale=None, bc_type="zero", **kwargs): # Init from abstract distribution class super().__init__(**kwargs) self.location = location self.scale = scale self._bc_type = bc_type # Ensure geometry has shape if not self.geometry.fun_shape or self.geometry.par_dim == 1: raise ValueError(f"Distribution {self.__class__.__name__} must be initialized with supported geometry (geometry of which the fun_shape is not None) and has parameter dimension greater than 1.") # Default physical_dim to geometry's dimension if not provided physical_dim = len(self.geometry.fun_shape) # Ensure provided physical dimension is either 1 or 2 if physical_dim not in [1, 2]: raise ValueError("Only physical dimension 1 or 2 supported.") self._physical_dim = physical_dim if self._physical_dim == 2: N = int(np.sqrt(self.dim)) num_nodes = (N, N) else: num_nodes = self.dim self._diff_op = FirstOrderFiniteDifference(num_nodes=num_nodes, bc_type=bc_type)
@property def location(self): return self._location @location.setter def location(self, value): self._location = force_ndarray(value, flatten=True)
[docs] def logpdf(self, x): Dx = self._diff_op @ (x-self.location) # g_logpr = (-2*Dx/(Dx**2 + gamma**2)) @ D return -len(Dx)*np.log(np.pi) + sum(np.log(self.scale) - np.log(Dx**2 + self.scale**2))
def _gradient(self, val, **kwargs): #Avoid complicated geometries that change the gradient. if not type(self.geometry) in _get_identity_geometries(): raise NotImplementedError("Gradient not implemented for distribution {} with geometry {}".format(self,self.geometry)) if not callable(self.location): # for prior diff = self._diff_op._matrix @ val return (-2*diff/(diff**2+self.scale**2)) @ self._diff_op._matrix else: warnings.warn('Gradient not implemented for {}'.format(type(self.location))) def _sample(self,N=1,rng=None): raise NotImplementedError("'CMRF.sample' is not implemented. Sampling can be performed with the 'sampler' module.")
# def cdf(self, x): # TODO # return 1/np.pi * np.atan((x-self.loc)/self.scale) # def sample(self): # TODO # return self.loc + self.scale*np.tan(np.pi*(np.random.rand(self.dim)-1/2))