CUQIpy 0.8.0.post0.dev2__py3-none-any.whl → 0.8.0.post0.dev13__py3-none-any.whl

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.

@@ -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
  cuqi/__init__.py,sha256=K0ss2HNqoLUX7wGpSZdaPKxIaKdRS452fcJm4D0pcEs,433
2
2
  cuqi/_messages.py,sha256=fzEBrZT2kbmfecBBPm7spVu7yHdxGARQB4QzXhJbCJ0,415
3
- cuqi/_version.py,sha256=MQSud900OC1ibNn98W5vCqROwklBvJj9r_-pOO0rKgM,508
3
+ cuqi/_version.py,sha256=8o64V7liXM5C7qTDSlG8846YAX74kBBM_o5Zjl9Zpvw,509
4
4
  cuqi/config.py,sha256=wcYvz19wkeKW2EKCGIKJiTpWt5kdaxyt4imyRkvtTRA,526
5
5
  cuqi/diagnostics.py,sha256=5OrbJeqpynqRXOe5MtOKKhe7EAVdOEpHIqHnlMW9G_c,3029
6
6
  cuqi/array/__init__.py,sha256=-EeiaiWGNsE3twRS4dD814BIlfxEsNkTCZUc5gjOXb0,30
@@ -16,16 +16,16 @@ cuqi/density/_density.py,sha256=BG7gtP0cbFYLVgjYQGkNAhM95PR5ocBVLKRlOVX2PyM,7253
16
16
  cuqi/distribution/__init__.py,sha256=f85DzOHPvGec9nr_AIfp_THSuC4WN8ZUJMSLZrKClG8,615
17
17
  cuqi/distribution/_beta.py,sha256=xQ6nURJqB20j1A8YNnpKO9BUcb-kKUdq8QCmljlm9l4,2980
18
18
  cuqi/distribution/_cauchy.py,sha256=UsVXYz8HhagXN5fIWSAIyELqhsJAX_-wk9kkRGgRmA8,3296
19
- cuqi/distribution/_cmrf.py,sha256=5j4vZ97N6OydrRv74qUi-sZGmfxJfbHKug3La5viVNo,3735
19
+ cuqi/distribution/_cmrf.py,sha256=tCbEulM_O7FB3C_W-3IqZp9zGHkTofCdFF0ybHc9UZI,3745
20
20
  cuqi/distribution/_custom.py,sha256=1ZFT3yjXsRKQJwNX3WwNDf2a9x13C9nIgxmq2wEOv4M,10657
21
21
  cuqi/distribution/_distribution.py,sha256=Qh8Yq-rtKh9xgog-2bo7d4-o0vaW_aAt5LaGbXlgX0U,17951
22
22
  cuqi/distribution/_gamma.py,sha256=GGsbIeHQhzUb1eTNeARcLXjJqcZ5iZWvaDaNsfJv9N0,1303
23
23
  cuqi/distribution/_gaussian.py,sha256=Ymllxg7ZQE24ss0oVgtPII4Hx4-xy3x1tAb01_-4i_U,33026
24
- cuqi/distribution/_gmrf.py,sha256=CFUjgN4b3mbyJHWDPCEO4zntBcF9Zq1cMgyzerM_3Wg,10385
24
+ cuqi/distribution/_gmrf.py,sha256=OwId8qQWEtmC2fxVhL4iBHZnc8ZCrZzfV6yGXDE3k30,9522
25
25
  cuqi/distribution/_inverse_gamma.py,sha256=XRcNGW_jzORL08V7VvtsuMUoQioBAGbN12qe8hCXJvg,3309
26
26
  cuqi/distribution/_joint_distribution.py,sha256=jRsV1Dt-pW6sG_xNqF0TugeVKDJY4Kh5aBLsIWfv394,15043
27
27
  cuqi/distribution/_laplace.py,sha256=5exLvlzJm2AgfvZ3KUSkjfwlGwwbsktBxP8z0iLMik8,1401
28
- cuqi/distribution/_lmrf.py,sha256=HKIGFIZBvbSF-28tP3DmrpPLDNb8-tVmsu7GJVZvRzk,3280
28
+ cuqi/distribution/_lmrf.py,sha256=rdGoQ-fPe1oW6Z29P-l3woq0NX3_RxUQ2rzm1VzemNM,3290
29
29
  cuqi/distribution/_lognormal.py,sha256=st1Uhf67qy2Seo65hA88JQ7lkEjQkW6KxznXahF_0SU,2844
30
30
  cuqi/distribution/_normal.py,sha256=UeoTtGDT7YSf4ZNo2amlVF9K-YQpYbf8q76jcRJTVFw,1914
31
31
  cuqi/distribution/_posterior.py,sha256=zAfL0GECxekZ2lBt1W6_LN0U_xskMwK4VNce5xAF7ig,5018
@@ -63,8 +63,8 @@ cuqi/testproblem/_testproblem.py,sha256=x769LwwRdJdzIiZkcQUGb_5-vynNTNALXWKato7s
63
63
  cuqi/utilities/__init__.py,sha256=EfxHLdsyDNugbmbzs43nV_AeKcycM9sVBjG9WZydagA,351
64
64
  cuqi/utilities/_get_python_variable_name.py,sha256=QwlBVj2koJRA8s8pWd554p7-ElcI7HUwY32HknaR92E,1827
65
65
  cuqi/utilities/_utilities.py,sha256=UaC-rWhevEzi6862uZdHNQoBV8fAgsLm4Fobb6ik81I,8025
66
- CUQIpy-0.8.0.post0.dev2.dist-info/LICENSE,sha256=kJWRPrtRoQoZGXyyvu50Uc91X6_0XRaVfT0YZssicys,10799
67
- CUQIpy-0.8.0.post0.dev2.dist-info/METADATA,sha256=JbhclAYo32_qopsHgTQCQ0w_IWMtKpoXSNQD9BoMv7E,18385
68
- CUQIpy-0.8.0.post0.dev2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
69
- CUQIpy-0.8.0.post0.dev2.dist-info/top_level.txt,sha256=AgmgMc6TKfPPqbjV0kvAoCBN334i_Lwwojc7HE3ZwD0,5
70
- CUQIpy-0.8.0.post0.dev2.dist-info/RECORD,,
66
+ CUQIpy-0.8.0.post0.dev13.dist-info/LICENSE,sha256=kJWRPrtRoQoZGXyyvu50Uc91X6_0XRaVfT0YZssicys,10799
67
+ CUQIpy-0.8.0.post0.dev13.dist-info/METADATA,sha256=3pWRkPFBPnXKK3yDk_5rQvVC-tllSZ9GtJwd0YD3Is0,18386
68
+ CUQIpy-0.8.0.post0.dev13.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
69
+ CUQIpy-0.8.0.post0.dev13.dist-info/top_level.txt,sha256=AgmgMc6TKfPPqbjV0kvAoCBN334i_Lwwojc7HE3ZwD0,5
70
+ CUQIpy-0.8.0.post0.dev13.dist-info/RECORD,,
cuqi/_version.py CHANGED
@@ -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