CUQIpy 0.8.0.post0.dev2__tar.gz → 0.8.0.post0.dev13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of CUQIpy might be problematic. Click here for more details.

Files changed (95) hide show
  1. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/CUQIpy.egg-info/PKG-INFO +1 -1
  2. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/PKG-INFO +1 -1
  3. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/_version.py +3 -3
  4. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_cmrf.py +1 -1
  5. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_gmrf.py +49 -56
  6. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_lmrf.py +1 -1
  7. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_MRFs.py +27 -1
  8. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_bayesian_inversion.py +1 -1
  9. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_distribution.py +4 -4
  10. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_distributions_shape.py +0 -3
  11. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_sampler.py +3 -0
  12. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/CUQIpy.egg-info/SOURCES.txt +0 -0
  13. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/CUQIpy.egg-info/dependency_links.txt +0 -0
  14. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/CUQIpy.egg-info/requires.txt +0 -0
  15. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/CUQIpy.egg-info/top_level.txt +0 -0
  16. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/LICENSE +0 -0
  17. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/README.md +0 -0
  18. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/__init__.py +0 -0
  19. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/_messages.py +0 -0
  20. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/array/__init__.py +0 -0
  21. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/array/_array.py +0 -0
  22. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/config.py +0 -0
  23. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/data/__init__.py +0 -0
  24. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/data/_data.py +0 -0
  25. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/data/astronaut.npz +0 -0
  26. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/data/camera.npz +0 -0
  27. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/data/cat.npz +0 -0
  28. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/data/satellite.mat +0 -0
  29. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/density/__init__.py +0 -0
  30. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/density/_density.py +0 -0
  31. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/diagnostics.py +0 -0
  32. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/__init__.py +0 -0
  33. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_beta.py +0 -0
  34. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_cauchy.py +0 -0
  35. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_custom.py +0 -0
  36. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_distribution.py +0 -0
  37. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_gamma.py +0 -0
  38. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_gaussian.py +0 -0
  39. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_inverse_gamma.py +0 -0
  40. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_joint_distribution.py +0 -0
  41. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_laplace.py +0 -0
  42. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_lognormal.py +0 -0
  43. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_normal.py +0 -0
  44. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_posterior.py +0 -0
  45. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/distribution/_uniform.py +0 -0
  46. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/geometry/__init__.py +0 -0
  47. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/geometry/_geometry.py +0 -0
  48. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/likelihood/__init__.py +0 -0
  49. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/likelihood/_likelihood.py +0 -0
  50. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/model/__init__.py +0 -0
  51. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/model/_model.py +0 -0
  52. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/operator/__init__.py +0 -0
  53. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/operator/_operator.py +0 -0
  54. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/pde/__init__.py +0 -0
  55. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/pde/_pde.py +0 -0
  56. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/problem/__init__.py +0 -0
  57. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/problem/_problem.py +0 -0
  58. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/__init__.py +0 -0
  59. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/_conjugate.py +0 -0
  60. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/_conjugate_approx.py +0 -0
  61. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/_cwmh.py +0 -0
  62. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/_gibbs.py +0 -0
  63. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/_hmc.py +0 -0
  64. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/_langevin_algorithm.py +0 -0
  65. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/_laplace_approximation.py +0 -0
  66. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/_mh.py +0 -0
  67. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/_pcn.py +0 -0
  68. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/_rto.py +0 -0
  69. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/sampler/_sampler.py +0 -0
  70. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/samples/__init__.py +0 -0
  71. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/samples/_samples.py +0 -0
  72. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/solver/__init__.py +0 -0
  73. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/solver/_solver.py +0 -0
  74. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/testproblem/__init__.py +0 -0
  75. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/testproblem/_testproblem.py +0 -0
  76. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/utilities/__init__.py +0 -0
  77. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/utilities/_get_python_variable_name.py +0 -0
  78. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/cuqi/utilities/_utilities.py +0 -0
  79. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/pyproject.toml +0 -0
  80. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/requirements.txt +0 -0
  81. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/setup.cfg +0 -0
  82. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/setup.py +0 -0
  83. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_abstract_distribution_density.py +0 -0
  84. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_density.py +0 -0
  85. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_geometry.py +0 -0
  86. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_joint_distribution.py +0 -0
  87. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_likelihood.py +0 -0
  88. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_model.py +0 -0
  89. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_pde.py +0 -0
  90. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_posterior.py +0 -0
  91. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_problem.py +0 -0
  92. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_samples.py +0 -0
  93. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_solver.py +0 -0
  94. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_testproblem.py +0 -0
  95. {CUQIpy-0.8.0.post0.dev2 → CUQIpy-0.8.0.post0.dev13}/tests/test_utilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: CUQIpy
3
- Version: 0.8.0.post0.dev2
3
+ Version: 0.8.0.post0.dev13
4
4
  Summary: Computational Uncertainty Quantification for Inverse problems in Python
5
5
  Maintainer-email: "Nicolai A. B. Riis" <nabr@dtu.dk>, "Jakob S. Jørgensen" <jakj@dtu.dk>, "Amal M. Alghamdi" <amaal@dtu.dk>, Chao Zhang <chaz@dtu.dk>
6
6
  License: Apache License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: CUQIpy
3
- Version: 0.8.0.post0.dev2
3
+ Version: 0.8.0.post0.dev13
4
4
  Summary: Computational Uncertainty Quantification for Inverse problems in Python
5
5
  Maintainer-email: "Nicolai A. B. Riis" <nabr@dtu.dk>, "Jakob S. Jørgensen" <jakj@dtu.dk>, "Amal M. Alghamdi" <amaal@dtu.dk>, Chao Zhang <chaz@dtu.dk>
6
6
  License: Apache License
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2023-12-07T11:04:12+0100",
11
+ "date": "2023-12-19T22:07:12+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "2a8abe27ac8ca7dcd377fb306230d4f1393d0550",
15
- "version": "0.8.0.post0.dev2"
14
+ "full-revisionid": "cdddb1ac7225b019877b440f2c84a5cf34190af5",
15
+ "version": "0.8.0.post0.dev13"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -44,7 +44,7 @@ class CMRF(Distribution):
44
44
 
45
45
  """
46
46
 
47
- def __init__(self, location, scale, bc_type="zero", **kwargs):
47
+ def __init__(self, location=None, scale=None, bc_type="zero", **kwargs):
48
48
  # Init from abstract distribution class
49
49
  super().__init__(**kwargs)
50
50
 
@@ -2,11 +2,12 @@ import numpy as np
2
2
  from scipy.sparse import diags, eye
3
3
  from scipy.sparse import linalg as splinalg
4
4
  from scipy.linalg import dft
5
- from cuqi.geometry import _DefaultGeometry1D, Image2D, _get_identity_geometries
5
+ from cuqi.geometry import _get_identity_geometries
6
6
  from cuqi.utilities import sparse_cholesky
7
7
  from cuqi import config
8
8
  from cuqi.operator import PrecisionFiniteDifference
9
9
  from cuqi.distribution import Distribution
10
+ from cuqi.utilities import force_ndarray
10
11
 
11
12
  class GMRF(Distribution):
12
13
  """ Gaussian Markov random field (GMRF).
@@ -19,9 +20,6 @@ class GMRF(Distribution):
19
20
  prec : float
20
21
  Precision of the GMRF.
21
22
 
22
- physical_dim : int
23
- The physical dimension of what the distribution represents (can take the values 1 or 2).
24
-
25
23
  bc_type : str
26
24
  The type of boundary conditions to use. Can be 'zero', 'periodic' or 'neumann'.
27
25
 
@@ -94,54 +92,67 @@ class GMRF(Distribution):
94
92
  For more details see: See Bardsley, J. (2018). Computational Uncertainty Quantification for Inverse Problems, Chapter 4.2.
95
93
 
96
94
  """
97
-
98
- def __init__(self, mean, prec, physical_dim=1, bc_type='zero', order=1, is_symmetric=True, **kwargs):
99
- super().__init__(is_symmetric=is_symmetric, **kwargs) #TODO: This calls Distribution __init__, should be replaced by calling Gaussian.__init__
100
95
 
101
- self.mean = mean.reshape(len(mean), 1)
96
+ def __init__(self, mean=None, prec=None, bc_type="zero", order=1, **kwargs):
97
+ # Init from abstract distribution class
98
+ super().__init__(**kwargs)
99
+
100
+ self.mean = mean
102
101
  self.prec = prec
103
- self._partition_size = int(len(mean)**(1/physical_dim))
104
- self._bc_type = bc_type # boundary conditions
102
+ self._bc_type = bc_type
103
+
104
+ # Ensure geometry has shape
105
+ if not self.geometry.fun_shape or self.geometry.par_dim == 1:
106
+ 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.")
107
+
108
+ # Default physical_dim to geometry's dimension if not provided
109
+ physical_dim = len(self.geometry.fun_shape)
110
+
111
+ # Ensure provided physical dimension is either 1 or 2
112
+ if physical_dim not in [1, 2]:
113
+ raise ValueError("Only physical dimension 1 or 2 supported.")
114
+
105
115
  self._physical_dim = physical_dim
106
-
107
- num_nodes = tuple(self._partition_size for _ in range(physical_dim))
108
- if physical_dim == 2: #TODO. Remove once _DefaultGeometry is implemented for 2D.
109
- if isinstance(self.geometry, _DefaultGeometry1D):
110
- self.geometry = Image2D(num_nodes)
111
116
 
112
- self._prec_op = PrecisionFiniteDifference(num_nodes, bc_type=bc_type, order=order)
113
- self._diff_op = self._prec_op._diff_op
117
+ if self._physical_dim == 2:
118
+ N = int(np.sqrt(self.dim))
119
+ num_nodes = (N, N)
120
+ else:
121
+ num_nodes = self.dim
122
+
123
+ self._prec_op = PrecisionFiniteDifference(num_nodes=num_nodes, bc_type=bc_type, order=order)
124
+ self._diff_op = self._prec_op._diff_op
114
125
 
115
126
  # compute Cholesky and det
116
127
  if (bc_type == 'zero'): # only for PSD matrices
117
128
  self._rank = self.dim
118
129
  self._chol = sparse_cholesky(self._prec_op.get_matrix()).T
119
130
  self._logdet = 2*sum(np.log(self._chol.diagonal()))
120
- # L_cholmod = cholesky(self.L, ordering_method='natural')
121
- # self.chol = L_cholmod
122
- # self.logdet = L_cholmod.logdet()
123
- #
124
- # np.log(np.linalg.det(self.L.todense()))
125
131
  elif (bc_type == 'periodic') or (bc_type == 'neumann'):
126
- # Print warning that periodic and Neumann boundary conditions are experimental
127
132
  print("Warning (GMRF): Periodic and Neumann boundary conditions are experimental. Sampling using LinearRTO may not produce fully accurate results.")
128
-
129
133
  eps = np.finfo(float).eps
130
134
  self._rank = self.dim - 1 #np.linalg.matrix_rank(self.L.todense())
131
135
  self._chol = sparse_cholesky(self._prec_op + np.sqrt(eps)*eye(self.dim, dtype=int)).T
132
- if (self.dim > config.MAX_DIM_INV): # approximate to avoid 'excesive' time
136
+ if (self.dim > config.MAX_DIM_INV): # approximate to avoid 'excessive' time
133
137
  self._logdet = 2*sum(np.log(self._chol.diagonal()))
134
138
  else:
135
- # eigval = eigvalsh(self.L.todense())
136
139
  self._L_eigval = splinalg.eigsh(self._prec_op.get_matrix(), self._rank, which='LM', return_eigenvectors=False)
137
140
  self._logdet = sum(np.log(self._L_eigval))
138
141
  else:
139
142
  raise ValueError('bc_type must be "zero", "periodic" or "neumann"')
140
143
 
144
+ @property
145
+ def mean(self):
146
+ return self._mean
147
+
148
+ @mean.setter
149
+ def mean(self, value):
150
+ self._mean = force_ndarray(value, flatten=True)
151
+
141
152
  @property
142
153
  def prec(self):
143
154
  return self._prec
144
-
155
+
145
156
  @prec.setter
146
157
  def prec(self, value):
147
158
  # We store the precision as a scalar to match existing code in this class,
@@ -153,33 +164,20 @@ class GMRF(Distribution):
153
164
  raise ValueError('Precision must be a scalar or a 1D array with a single scalar element.')
154
165
  self._prec = value
155
166
 
156
- @property
157
- def dim(self):
158
- if self._physical_dim == 1:
159
- return self._partition_size
160
- elif self._physical_dim==2:
161
- return self._partition_size**2
162
- raise ValueError("attribute dom can be either 1 or 2")
163
-
164
167
  def logpdf(self, x):
165
- mean = self.mean.flatten()
168
+ mean = self.mean
166
169
  const = 0.5*(self._rank*(np.log(self.prec)-np.log(2*np.pi)) + self._logdet)
167
- y = const - 0.5*( self.prec*((x-mean).T @ (self._prec_op @ (x-mean))) )
168
- # = sps.multivariate_normal.logpdf(x.T, self.mean.flatten(), np.linalg.inv(self.prec*self.L.todense()))
169
- return y
170
-
171
- def pdf(self, x):
172
- # = sps.multivariate_normal.pdf(x.T, self.mean.flatten(), np.linalg.inv(self.prec*self.L.todense()))
173
- return np.exp(self.logpdf(x))
170
+ return const - 0.5*( self.prec*((x-mean).T @ (self._prec_op @ (x-mean))) )
174
171
 
175
172
  def _gradient(self, x):
176
173
  #Avoid complicated geometries that change the gradient.
177
174
  if not type(self.geometry) in _get_identity_geometries():
178
175
  raise NotImplementedError("Gradient not implemented for distribution {} with geometry {}".format(self,self.geometry))
179
176
 
180
- if not callable(self.mean):
181
- mean = self.mean.flatten()
182
- return -(self.prec*self._prec_op) @ (x-mean)
177
+ if not callable(self.mean): # for prior
178
+ return -(self.prec*self._prec_op) @ (x-self.mean)
179
+ else:
180
+ NotImplementedError("Gradient not implemented for mean {}".format(type(self.mean)))
183
181
 
184
182
  def _sample(self, N=1, rng=None):
185
183
  if (self._bc_type == 'zero'):
@@ -190,10 +188,9 @@ class GMRF(Distribution):
190
188
  xi = np.random.randn(self.dim, N) # standard Gaussian
191
189
 
192
190
  if N == 1:
193
- s = self.mean.flatten() + (1/np.sqrt(self.prec))*splinalg.spsolve(self._chol.T, xi)
194
- else:
195
191
  s = self.mean + (1/np.sqrt(self.prec))*splinalg.spsolve(self._chol.T, xi)
196
- # s = self.mean + (1/np.sqrt(self.prec))*L_cholmod.solve_Lt(xi, use_LDLt_decomposition=False)
192
+ else:
193
+ s = self.mean[:, np.newaxis] + (1/np.sqrt(self.prec))*splinalg.spsolve(self._chol.T, xi)
197
194
 
198
195
  elif (self._bc_type == 'periodic'):
199
196
 
@@ -206,12 +203,9 @@ class GMRF(Distribution):
206
203
  xi = np.random.randn(self.dim, N) + 1j*np.random.randn(self.dim, N)
207
204
 
208
205
  F = dft(self.dim, scale='sqrtn') # unitary DFT matrix
209
- # eigv = eigvalsh(self.L.todense()) # splinalg.eigsh(self.L, self.rank, return_eigenvectors=False)
210
206
  eigv = np.hstack([self._L_eigval, self._L_eigval[-1]]) # repeat last eigval to complete dim
211
207
  L_sqrt = diags(np.sqrt(eigv))
212
- s = self.mean + (1/np.sqrt(self.prec))*np.real(F.conj() @ splinalg.spsolve(L_sqrt, xi))
213
- # L_sqrt = pinvh(np.diag(np.sqrt(eigv)))
214
- # s = self.mean + (1/np.sqrt(self.prec))*np.real(F.conj() @ (L_sqrt @ xi))
208
+ s = self.mean[:, np.newaxis] + (1/np.sqrt(self.prec))*np.real(F.conj() @ splinalg.spsolve(L_sqrt, xi))
215
209
 
216
210
  elif (self._bc_type == 'neumann'):
217
211
 
@@ -220,7 +214,7 @@ class GMRF(Distribution):
220
214
  else:
221
215
  xi = np.random.randn(self._diff_op.shape[0], N) # standard Gaussian
222
216
 
223
- s = self.mean + (1/np.sqrt(self.prec))* \
217
+ s = self.mean[:, np.newaxis] + (1/np.sqrt(self.prec))* \
224
218
  splinalg.spsolve(self._chol.T, (splinalg.spsolve(self._chol, (self._diff_op.T @ xi))))
225
219
  else:
226
220
  raise TypeError('Unexpected BC type (choose from zero, periodic, neumann or none)')
@@ -233,5 +227,4 @@ class GMRF(Distribution):
233
227
 
234
228
  @property
235
229
  def sqrtprecTimesMean(self):
236
- return (self.sqrtprec@self.mean).flatten()
237
-
230
+ return (self.sqrtprec@self.mean)
@@ -40,7 +40,7 @@ class LMRF(Distribution):
40
40
  prior = cuqi.distribution.LMRF(location=0, scale=0.1, geometry=128)
41
41
 
42
42
  """
43
- def __init__(self, location, scale, bc_type="zero", **kwargs):
43
+ def __init__(self, location=None, scale=None, bc_type="zero", **kwargs):
44
44
  # Init from abstract distribution class
45
45
  super().__init__(**kwargs)
46
46
 
@@ -1,5 +1,5 @@
1
1
  import pytest
2
- from cuqi.distribution import LMRF, CMRF
2
+ from cuqi.distribution import LMRF, CMRF, GMRF
3
3
  from cuqi.geometry import Image2D, Geometry, _DefaultGeometry2D
4
4
  import numpy as np
5
5
 
@@ -68,4 +68,30 @@ def test_CMRF_dim_vs_ndarray_location():
68
68
  assert cmrf1.logd(np.zeros(16)) == cmrf2.logd(np.zeros(16))
69
69
  assert np.allclose(cmrf1.gradient(np.zeros(16)),cmrf2.gradient(np.zeros(16)))
70
70
 
71
+ # GMRF tests
72
+ def test_GMRF_should_not_allow_None_or_scalar_geometry():
73
+ with pytest.raises(ValueError, match="supported geometry"):
74
+ GMRF(0, 0.2)
75
+
76
+ @pytest.mark.parametrize("physical_dim", [-1, 0, 3])
77
+ def test_GMRF_invalid_physical_dim_raises_error(physical_dim):
78
+ with pytest.raises(ValueError, match="Only physical dimension 1 or 2 supported."):
79
+ GMRF(0, 0.2, geometry=Image3D())
80
+
81
+ def test_GMRF_mismatched_location_and_geometry_raises_error():
82
+ image_2d_geometry = Image2D((8, 8))
83
+ with pytest.raises(TypeError):
84
+ GMRF(np.zeros(3), 0.2, geometry=image_2d_geometry)
85
+
86
+ def test_GMRF_default_geometry_is_replaced_when_physical_dim_is_2():
87
+ gmrf = GMRF(0, 0.2, geometry=(4,4))
88
+ assert isinstance(gmrf.geometry, _DefaultGeometry2D)
89
+
90
+ def test_GMRF_dim_vs_ndarray_location():
91
+ gmrf1 = GMRF(0, 0.2, geometry=(4,4))
92
+ gmrf2 = GMRF(np.zeros(16), 0.2, geometry=Image2D((4,4)))
93
+ assert gmrf1.dim == gmrf2.dim
94
+ assert gmrf1.logd(np.zeros(16)) == gmrf2.logd(np.zeros(16))
95
+ assert np.allclose(gmrf1.gradient(np.zeros(16)),gmrf2.gradient(np.zeros(16)))
96
+
71
97
 
@@ -13,7 +13,7 @@ from cuqi.density import Density
13
13
  @pytest.mark.parametrize("TP_type, phantom, prior, Ns",
14
14
  [
15
15
  (Deconvolution1D, "gauss", Gaussian(np.zeros(128), 0.071**2), 20),
16
- (Deconvolution1D, "gauss", GMRF(np.zeros(128), 100, 1, "zero"), 20),
16
+ (Deconvolution1D, "gauss", GMRF(np.zeros(128), 100, "zero"), 20),
17
17
  (Deconvolution1D, "square", LMRF(0, 0.005, geometry=128), 100),
18
18
  (Deconvolution1D, "square", CMRF(np.zeros(128), 0.01), 50),
19
19
  ])
@@ -113,7 +113,7 @@ def test_Gaussian_rng(mean,std,R):
113
113
  cov = np.diag(std) @ (R @ np.diag(std))
114
114
  assert np.allclose(cuqi.distribution.Gaussian(mean,cov).sample(10).samples,cuqi.distribution.Gaussian(mean,cov).sample(10,rng=rng).samples)
115
115
 
116
- @pytest.mark.parametrize("dist",[cuqi.distribution.GMRF(np.ones(128),35,1,'zero'),cuqi.distribution.GMRF(np.ones(128),35,1,'periodic'),cuqi.distribution.GMRF(np.ones(128),35,1,'neumann')])
116
+ @pytest.mark.parametrize("dist",[cuqi.distribution.GMRF(np.ones(128),35,'zero'),cuqi.distribution.GMRF(np.ones(128),35,'periodic'),cuqi.distribution.GMRF(np.ones(128),35,'neumann')])
117
117
  def test_GMRF_rng(dist):
118
118
  np.random.seed(3)
119
119
  rng = np.random.RandomState(3)
@@ -237,7 +237,7 @@ def test_Gaussians_vs_GMRF(prec, GMRF_order):
237
237
  X_cov = cuqi.distribution.Gaussian(np.zeros(dim), cov)
238
238
  X_sqrtprec = cuqi.distribution.Gaussian(np.zeros(dim), sqrtprec=sqrtprec)
239
239
  X_sqrtcov = cuqi.distribution.Gaussian(np.zeros(dim), sqrtcov=sqrtcov)
240
- X_GMRF = cuqi.distribution.GMRF(np.zeros(dim), 1, 1, 'zero', order=GMRF_order)
240
+ X_GMRF = cuqi.distribution.GMRF(np.zeros(dim), 1, 'zero', order=GMRF_order)
241
241
 
242
242
  # logpdfs for full matrix
243
243
  x0 = np.random.randn(dim)
@@ -575,7 +575,7 @@ def test_Gaussian_Cov_sample(C):
575
575
  cuqi.distribution.Gaussian(np.zeros(2),
576
576
  np.array([[1.0, 0.7],
577
577
  [0.7, 1.]])),
578
- cuqi.distribution.GMRF(np.zeros(2), 0.1, 1)])
578
+ cuqi.distribution.GMRF(np.zeros(2), 0.1)])
579
579
  def test_enable_FD_gradient_distributions(dist):
580
580
  """Test that the distribution FD gradient is close to the exact gradient"""
581
581
  x = np.array([0.1, 0.3])
@@ -596,7 +596,7 @@ def test_enable_FD_gradient_distributions(dist):
596
596
  [cuqi.distribution.Gaussian(np.zeros(6), np.eye(6)),
597
597
  cuqi.distribution.Beta(np.ones(6)*2, 5),
598
598
  cuqi.distribution.Lognormal(np.ones(6)*.1, 4),
599
- cuqi.distribution.GMRF(np.zeros(6), 0.1, 1)])
599
+ cuqi.distribution.GMRF(np.zeros(6), 0.1)])
600
600
  @pytest.mark.parametrize("y",
601
601
  [cuqi.distribution.Gaussian(np.zeros(6), np.eye(6)),
602
602
  cuqi.distribution.Lognormal(np.zeros(6), 4)])
@@ -17,16 +17,13 @@ ignore_list = [
17
17
 
18
18
  # Define cases to skip (these are TODO)
19
19
  skip_logd = [
20
- cuqi.distribution.GMRF, # Mean must allow scalar values
21
20
  cuqi.distribution.Gamma # Missing force_ndarray
22
21
  ]
23
22
  skip_sample = [
24
- cuqi.distribution.GMRF, # Mean must allow scalar values
25
23
  cuqi.distribution.Gamma, # Missing force_ndarray
26
24
  cuqi.distribution.Lognormal,
27
25
  ]
28
26
  skip_gradient = [
29
- cuqi.distribution.GMRF, # Mean must allow scalar values
30
27
  cuqi.distribution.Gamma, # Missing force_ndarray
31
28
  cuqi.distribution.Lognormal,
32
29
  cuqi.distribution.InverseGamma,
@@ -479,6 +479,9 @@ def test_Gibbs_regression(copy_reference):
479
479
  "data/Gibbs_original_code_results_win.npz")
480
480
  samples_orig = np.load(samples_orig_file)
481
481
 
482
+ # Save results
483
+ #np.savez("Gibbs_original_code_results_win.npz", samples["d"].samples)
484
+
482
485
  assert(np.allclose(samples["d"].samples, samples_orig["arr_0"]))
483
486
 
484
487
  def test_Gibbs_continue_sampling():