CUQIpy 1.3.0.post0.dev298__py3-none-any.whl → 1.4.0.post0.dev92__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.
Files changed (68) hide show
  1. cuqi/__init__.py +2 -0
  2. cuqi/_version.py +3 -3
  3. cuqi/algebra/__init__.py +2 -0
  4. cuqi/{experimental/algebra/_randomvariable.py → algebra/_random_variable.py} +4 -4
  5. cuqi/density/_density.py +9 -1
  6. cuqi/distribution/_distribution.py +25 -16
  7. cuqi/distribution/_joint_distribution.py +99 -14
  8. cuqi/distribution/_posterior.py +9 -0
  9. cuqi/experimental/__init__.py +1 -4
  10. cuqi/experimental/_recommender.py +4 -4
  11. cuqi/geometry/__init__.py +2 -0
  12. cuqi/{experimental/geometry/_productgeometry.py → geometry/_product_geometry.py} +1 -1
  13. cuqi/implicitprior/__init__.py +1 -1
  14. cuqi/implicitprior/_restorator.py +35 -1
  15. cuqi/legacy/__init__.py +2 -0
  16. cuqi/legacy/sampler/__init__.py +11 -0
  17. cuqi/legacy/sampler/_conjugate.py +55 -0
  18. cuqi/legacy/sampler/_conjugate_approx.py +52 -0
  19. cuqi/legacy/sampler/_cwmh.py +196 -0
  20. cuqi/legacy/sampler/_gibbs.py +231 -0
  21. cuqi/legacy/sampler/_hmc.py +335 -0
  22. cuqi/legacy/sampler/_langevin_algorithm.py +198 -0
  23. cuqi/legacy/sampler/_laplace_approximation.py +184 -0
  24. cuqi/legacy/sampler/_mh.py +190 -0
  25. cuqi/legacy/sampler/_pcn.py +244 -0
  26. cuqi/legacy/sampler/_rto.py +284 -0
  27. cuqi/legacy/sampler/_sampler.py +182 -0
  28. cuqi/likelihood/_likelihood.py +1 -1
  29. cuqi/model/_model.py +225 -90
  30. cuqi/pde/__init__.py +4 -0
  31. cuqi/pde/_observation_map.py +36 -0
  32. cuqi/pde/_pde.py +52 -21
  33. cuqi/problem/_problem.py +87 -80
  34. cuqi/sampler/__init__.py +120 -8
  35. cuqi/sampler/_conjugate.py +376 -35
  36. cuqi/sampler/_conjugate_approx.py +40 -16
  37. cuqi/sampler/_cwmh.py +132 -138
  38. cuqi/{experimental/mcmc → sampler}/_direct.py +1 -1
  39. cuqi/sampler/_gibbs.py +276 -130
  40. cuqi/sampler/_hmc.py +328 -201
  41. cuqi/sampler/_langevin_algorithm.py +282 -98
  42. cuqi/sampler/_laplace_approximation.py +87 -117
  43. cuqi/sampler/_mh.py +47 -157
  44. cuqi/sampler/_pcn.py +65 -213
  45. cuqi/sampler/_rto.py +206 -140
  46. cuqi/sampler/_sampler.py +540 -135
  47. {cuqipy-1.3.0.post0.dev298.dist-info → cuqipy-1.4.0.post0.dev92.dist-info}/METADATA +1 -1
  48. cuqipy-1.4.0.post0.dev92.dist-info/RECORD +101 -0
  49. cuqi/experimental/algebra/__init__.py +0 -2
  50. cuqi/experimental/geometry/__init__.py +0 -1
  51. cuqi/experimental/mcmc/__init__.py +0 -122
  52. cuqi/experimental/mcmc/_conjugate.py +0 -396
  53. cuqi/experimental/mcmc/_conjugate_approx.py +0 -76
  54. cuqi/experimental/mcmc/_cwmh.py +0 -190
  55. cuqi/experimental/mcmc/_gibbs.py +0 -374
  56. cuqi/experimental/mcmc/_hmc.py +0 -460
  57. cuqi/experimental/mcmc/_langevin_algorithm.py +0 -382
  58. cuqi/experimental/mcmc/_laplace_approximation.py +0 -154
  59. cuqi/experimental/mcmc/_mh.py +0 -80
  60. cuqi/experimental/mcmc/_pcn.py +0 -89
  61. cuqi/experimental/mcmc/_rto.py +0 -306
  62. cuqi/experimental/mcmc/_sampler.py +0 -564
  63. cuqipy-1.3.0.post0.dev298.dist-info/RECORD +0 -100
  64. /cuqi/{experimental/algebra/_ast.py → algebra/_abstract_syntax_tree.py} +0 -0
  65. /cuqi/{experimental/algebra/_orderedset.py → algebra/_ordered_set.py} +0 -0
  66. {cuqipy-1.3.0.post0.dev298.dist-info → cuqipy-1.4.0.post0.dev92.dist-info}/WHEEL +0 -0
  67. {cuqipy-1.3.0.post0.dev298.dist-info → cuqipy-1.4.0.post0.dev92.dist-info}/licenses/LICENSE +0 -0
  68. {cuqipy-1.3.0.post0.dev298.dist-info → cuqipy-1.4.0.post0.dev92.dist-info}/top_level.txt +0 -0
@@ -1,382 +0,0 @@
1
- import numpy as np
2
- import cuqi
3
- from cuqi.experimental.mcmc import Sampler
4
- from cuqi.implicitprior import RestorationPrior, MoreauYoshidaPrior
5
- from cuqi.array import CUQIarray
6
- from copy import copy
7
-
8
- class ULA(Sampler): # Refactor to Proposal-based sampler?
9
- """Unadjusted Langevin algorithm (ULA) (Roberts and Tweedie, 1996)
10
-
11
- It approximately samples a distribution given its logpdf gradient based on
12
- the Langevin diffusion dL_t = dW_t + 1/2*Nabla target.logd(L_t)dt, where
13
- W_t is the `dim`-dimensional standard Brownian motion.
14
- ULA results from the Euler-Maruyama discretization of this Langevin stochastic
15
- differential equation (SDE).
16
-
17
- For more details see: Roberts, G. O., & Tweedie, R. L. (1996). Exponential convergence
18
- of Langevin distributions and their discrete approximations. Bernoulli, 341-363.
19
-
20
- Parameters
21
- ----------
22
-
23
- target : `cuqi.distribution.Distribution`
24
- The target distribution to sample. Must have logd and gradient method. Custom logpdfs
25
- and gradients are supported by using a :class:`cuqi.distribution.UserDefinedDistribution`.
26
-
27
- initial_point : ndarray
28
- Initial parameters. *Optional*
29
-
30
- scale : float
31
- The Langevin diffusion discretization time step (In practice, scale must
32
- be smaller than 1/L, where L is the Lipschitz of the gradient of the log
33
- target density, logd).
34
-
35
- callback : callable, optional
36
- A function that will be called after each sampling step. It can be useful for monitoring the sampler during sampling.
37
- The function should take three arguments: the sampler object, the index of the current sampling step, the total number of requested samples. The last two arguments are integers. An example of the callback function signature is: `callback(sampler, sample_index, num_of_samples)`.
38
-
39
-
40
- Example
41
- -------
42
- .. code-block:: python
43
-
44
- # Parameters
45
- dim = 5 # Dimension of distribution
46
- mu = np.arange(dim) # Mean of Gaussian
47
- std = 1 # standard deviation of Gaussian
48
-
49
- # Logpdf function
50
- logpdf_func = lambda x: -1/(std**2)*np.sum((x-mu)**2)
51
- gradient_func = lambda x: -2/(std**2)*(x - mu)
52
-
53
- # Define distribution from logpdf and gradient as UserDefinedDistribution
54
- target = cuqi.distribution.UserDefinedDistribution(dim=dim, logpdf_func=logpdf_func,
55
- gradient_func=gradient_func)
56
-
57
- # Set up sampler
58
- sampler = cuqi.experimental.mcmc.ULA(target, scale=1/dim**2)
59
-
60
- # Sample
61
- sampler.sample(2000)
62
-
63
- A Deblur example can be found in demos/demo27_ULA.py
64
- # TODO: update demo once sampler merged
65
- """
66
-
67
- _STATE_KEYS = Sampler._STATE_KEYS.union({'scale', 'current_target_grad'})
68
-
69
- def __init__(self, target=None, scale=1.0, **kwargs):
70
-
71
- super().__init__(target, **kwargs)
72
- self.initial_scale = scale
73
-
74
- def _initialize(self):
75
- self.scale = self.initial_scale
76
- self.current_target_grad = self._eval_target_grad(self.current_point)
77
-
78
- def validate_target(self):
79
- try:
80
- self._eval_target_grad(np.ones(self.dim))
81
- pass
82
- except (NotImplementedError, AttributeError):
83
- raise ValueError("The target needs to have a gradient method")
84
-
85
- def _eval_target_logd(self, x):
86
- return None
87
-
88
- def _eval_target_grad(self, x):
89
- return self.target.gradient(x)
90
-
91
- def _accept_or_reject(self, x_star, target_eval_star, target_grad_star):
92
- """
93
- Accepts the proposed state and updates the sampler's state accordingly, i.e.,
94
- current_point, current_target_eval, and current_target_grad_eval.
95
-
96
- Parameters
97
- ----------
98
- x_star :
99
- The proposed state
100
-
101
- target_eval_star:
102
- The log likelihood evaluated at x_star
103
-
104
- target_grad_star:
105
- The gradient of log likelihood evaluated at x_star
106
-
107
- Returns
108
- -------
109
- scalar
110
- 1 (accepted)
111
- """
112
-
113
- self.current_point = x_star
114
- self.current_target_grad = target_grad_star
115
- acc = 1
116
-
117
- return acc
118
-
119
- def step(self):
120
- # propose state
121
- xi = cuqi.distribution.Normal(mean=np.zeros(self.dim), std=np.sqrt(self.scale)).sample()
122
- x_star = self.current_point + 0.5*self.scale*self.current_target_grad + xi
123
-
124
- # evaluate target
125
- target_eval_star = self._eval_target_logd(x_star)
126
- target_grad_star = self._eval_target_grad(x_star)
127
-
128
- # accept or reject proposal
129
- acc = self._accept_or_reject(x_star, target_eval_star, target_grad_star)
130
-
131
- return acc
132
-
133
- def tune(self, skip_len, update_count):
134
- pass
135
-
136
-
137
- class MALA(ULA): # Refactor to Proposal-based sampler?
138
- """ Metropolis-adjusted Langevin algorithm (MALA) (Roberts and Tweedie, 1996)
139
-
140
- Samples a distribution given its logd and gradient (up to a constant) based on
141
- Langevin diffusion dL_t = dW_t + 1/2*Nabla target.logd(L_t)dt,
142
- W_t is the `dim`-dimensional standard Brownian motion.
143
- A sample is firstly proposed by ULA and is then accepted or rejected according
144
- to a Metropolis–Hastings step.
145
- This accept-reject step allows us to remove the asymptotic bias of ULA.
146
-
147
- For more details see: Roberts, G. O., & Tweedie, R. L. (1996). Exponential convergence
148
- of Langevin distributions and their discrete approximations. Bernoulli, 341-363.
149
-
150
- Parameters
151
- ----------
152
-
153
- target : `cuqi.distribution.Distribution`
154
- The target distribution to sample. Must have logpdf and gradient method. Custom logpdfs
155
- and gradients are supported by using a :class:`cuqi.distribution.UserDefinedDistribution`.
156
-
157
- initial_point : ndarray
158
- Initial parameters. *Optional*
159
-
160
- scale : float
161
- The Langevin diffusion discretization time step (In practice, scale must
162
- be smaller than 1/L, where L is the Lipschitz of the gradient of the log
163
- target density, logd).
164
-
165
- callback : callable, optional
166
- A function that will be called after each sampling step. It can be useful for monitoring the sampler during sampling.
167
- The function should take three arguments: the sampler object, the index of the current sampling step, the total number of requested samples. The last two arguments are integers. An example of the callback function signature is: `callback(sampler, sample_index, num_of_samples)`.
168
-
169
-
170
- Example
171
- -------
172
- .. code-block:: python
173
-
174
- # Parameters
175
- dim = 5 # Dimension of distribution
176
- mu = np.arange(dim) # Mean of Gaussian
177
- std = 1 # standard deviation of Gaussian
178
-
179
- # Logpdf function
180
- logpdf_func = lambda x: -1/(std**2)*np.sum((x-mu)**2)
181
- gradient_func = lambda x: -2/(std**2)*(x-mu)
182
-
183
- # Define distribution from logpdf as UserDefinedDistribution (sample and gradients also supported)
184
- target = cuqi.distribution.UserDefinedDistribution(dim=dim, logpdf_func=logpdf_func,
185
- gradient_func=gradient_func)
186
-
187
- # Set up sampler
188
- sampler = cuqi.experimental.mcmc.MALA(target, scale=1/5**2)
189
-
190
- # Sample
191
- sampler.sample(2000)
192
-
193
- A Deblur example can be found in demos/demo28_MALA.py
194
- # TODO: update demo once sampler merged
195
- """
196
-
197
- _STATE_KEYS = ULA._STATE_KEYS.union({'current_target_logd'})
198
-
199
- def _initialize(self):
200
- super()._initialize()
201
- self.current_target_logd = self.target.logd(self.current_point)
202
-
203
- def _eval_target_logd(self, x):
204
- return self.target.logd(x)
205
-
206
- def _accept_or_reject(self, x_star, target_eval_star, target_grad_star):
207
- """
208
- Accepts the proposed state according to a Metropolis step and updates
209
- the sampler's state accordingly, i.e., current_point, current_target_eval,
210
- and current_target_grad_eval.
211
-
212
- Parameters
213
- ----------
214
- x_star :
215
- The proposed state
216
-
217
- target_eval_star:
218
- The log likelihood evaluated at x_star
219
-
220
- target_grad_star:
221
- The gradient of log likelihood evaluated at x_star
222
-
223
- Returns
224
- -------
225
- scaler
226
- 1 if accepted, 0 otherwise
227
- """
228
- log_target_ratio = target_eval_star - self.current_target_logd
229
- log_prop_ratio = self._log_proposal(self.current_point, x_star, target_grad_star) \
230
- - self._log_proposal(x_star, self.current_point, self.current_target_grad)
231
- log_alpha = min(0, log_target_ratio + log_prop_ratio)
232
-
233
- # accept/reject with Metropolis
234
- acc = 0
235
- log_u = np.log(np.random.rand())
236
- if (log_u <= log_alpha) and \
237
- (not np.isnan(target_eval_star)) and \
238
- (not np.isinf(target_eval_star)):
239
- self.current_point = x_star
240
- self.current_target_logd = target_eval_star
241
- self.current_target_grad = target_grad_star
242
- acc = 1
243
- return acc
244
-
245
- def tune(self, skip_len, update_count):
246
- pass
247
-
248
- def _log_proposal(self, theta_star, theta_k, g_logpi_k):
249
- mu = theta_k + ((self.scale)/2)*g_logpi_k
250
- misfit = theta_star - mu
251
- return -0.5*((1/(self.scale))*(misfit.T @ misfit))
252
-
253
-
254
- class MYULA(ULA):
255
- """Moreau-Yoshida Unadjusted Langevin algorithm (MYUULA) (Durmus et al., 2018)
256
-
257
- Samples a smoothed target distribution given its smoothed logpdf gradient.
258
- It is based on the Langevin diffusion dL_t = dW_t + 1/2*Nabla target.logd(L_t)dt,
259
- where W_t is a `dim`-dimensional standard Brownian motion.
260
- It targets a differentiable density (partially) smoothed by the Moreau-Yoshida
261
- envelope. The smoothed target density can be made arbitrarily closed to the
262
- true unsmoothed target density.
263
-
264
- For more details see: Durmus, Alain, Eric Moulines, and Marcelo Pereyra.
265
- "Efficient Bayesian
266
- computation by proximal Markov chain Monte Carlo: when Langevin meets Moreau."
267
- SIAM Journal on Imaging Sciences 11.1 (2018): 473-506.
268
-
269
- Parameters
270
- ----------
271
-
272
- target : `cuqi.distribution.Distribution`
273
- The target distribution to sample from. The target distribution results from
274
- a differentiable likelihood and prior of type RestorationPrior.
275
-
276
- initial_point : ndarray
277
- Initial parameters. *Optional*
278
-
279
- scale : float
280
- The Langevin diffusion discretization time step (In practice, scale must
281
- be smaller than 1/L, where L is the Lipschitz of the gradient of the log
282
- target density, logd).
283
-
284
- smoothing_strength : float
285
- This parameter controls the smoothing strength of MYULA.
286
-
287
- callback : callable, optional
288
- A function that will be called after each sampling step. It can be useful for monitoring the sampler during sampling.
289
- The function should take three arguments: the sampler object, the index of the current sampling step, the total number of requested samples. The last two arguments are integers. An example of the callback function signature is: `callback(sampler, sample_index, num_of_samples)`.
290
-
291
- A Deblur example can be found in demos/howtos/myula.py
292
- # TODO: update demo once sampler merged
293
- """
294
- def __init__(self, target=None, scale=1.0, smoothing_strength=0.1, **kwargs):
295
- self.smoothing_strength = smoothing_strength
296
- super().__init__(target=target, scale=scale, **kwargs)
297
-
298
- @Sampler.target.setter
299
- def target(self, value):
300
- """ Set the target density. Runs validation of the target. """
301
- self._target = value
302
-
303
- if self._target is not None:
304
- # Create a smoothed target
305
- self._smoothed_target = self._create_smoothed_target(value)
306
-
307
- # Validate the target
308
- self.validate_target()
309
-
310
- def _create_smoothed_target(self, value):
311
- """ Create a smoothed target using a Moreau-Yoshida envelope. """
312
- copied_value = copy(value)
313
- if isinstance(copied_value.prior, RestorationPrior):
314
- # Acceess the prior name
315
- name = value.prior.name
316
- copied_value.prior = MoreauYoshidaPrior(
317
- copied_value.prior,
318
- self.smoothing_strength,
319
- name=name)
320
- return copied_value
321
-
322
- def validate_target(self):
323
- # Call ULA target validation
324
- super().validate_target()
325
-
326
- # Additional validation for MYULA target
327
- if isinstance(self.target.prior, MoreauYoshidaPrior):
328
- raise ValueError(("The prior is already smoothed, apply"
329
- " ULA when using a MoreauYoshidaPrior."))
330
- if not hasattr(self.target.prior, "restore"):
331
- raise NotImplementedError(
332
- ("Using MYULA with a prior that does not have a restore method"
333
- " is not supported.")
334
- )
335
-
336
- def _eval_target_grad(self, x):
337
- return self._smoothed_target.gradient(x)
338
-
339
- class PnPULA(MYULA):
340
- """Plug-and-Play Unadjusted Langevin algorithm (PnP-ULA)
341
- (Laumont et al., 2022)
342
-
343
- Samples a smoothed target distribution given its smoothed logpdf gradient based on
344
- Langevin diffusion dL_t = dW_t + 1/2*Nabla target.logd(L_t)dt, where W_t is
345
- a `dim`-dimensional standard Brownian motion.
346
- It targets a differentiable density (partially) smoothed by a convolution
347
- with Gaussian kernel with zero mean and smoothing_strength variance. The
348
- smoothed target density can be made arbitrarily closed to the
349
- true unsmoothed target density.
350
-
351
- For more details see: Laumont, R., Bortoli, V. D., Almansa, A., Delon, J.,
352
- Durmus, A., & Pereyra, M. (2022). Bayesian imaging using plug & play priors:
353
- when Langevin meets Tweedie. SIAM Journal on Imaging Sciences, 15(2), 701-737.
354
-
355
- Parameters
356
- ----------
357
-
358
- target : `cuqi.distribution.Distribution`
359
- The target distribution to sample. The target distribution result from
360
- a differentiable likelihood and prior of type RestorationPrior.
361
-
362
- initial_point : ndarray
363
- Initial parameters. *Optional*
364
-
365
- scale : float
366
- The Langevin diffusion discretization time step (In practice, a scale of
367
- 1/L, where L is the Lipschitz of the gradient of the log target density
368
- is recommended but not guaranteed to be the optimal choice).
369
-
370
- smoothing_strength : float
371
- This parameter controls the smoothing strength of PnP-ULA.
372
-
373
-
374
- callback : callable, optional
375
- A function that will be called after each sampling step. It can be useful for monitoring the sampler during sampling.
376
- The function should take three arguments: the sampler object, the index of the current sampling step, the total number of requested samples. The last two arguments are integers. An example of the callback function signature is: `callback(sampler, sample_index, num_of_samples)`.
377
-
378
- # TODO: update demo once sampler merged
379
- """
380
- def __init__ (self, target=None, scale=1.0, smoothing_strength=0.1, **kwargs):
381
- super().__init__(target=target, scale=scale,
382
- smoothing_strength=smoothing_strength, **kwargs)
@@ -1,154 +0,0 @@
1
- import scipy as sp
2
- import numpy as np
3
- import cuqi
4
- from cuqi.solver import CGLS
5
- from cuqi.experimental.mcmc import Sampler
6
-
7
- class UGLA(Sampler):
8
- """ Unadjusted (Gaussian) Laplace Approximation sampler
9
-
10
- Samples an approximate posterior where the prior is approximated
11
- by a Gaussian distribution. The likelihood must be Gaussian.
12
-
13
- Currently only works for LMRF priors.
14
-
15
- The inner solver is Conjugate Gradient Least Squares (CGLS) solver.
16
-
17
- For more details see: Uribe, Felipe, et al. A hybrid Gibbs sampler for edge-preserving
18
- tomographic reconstruction with uncertain view angles. SIAM/ASA Journal on UQ,
19
- https://doi.org/10.1137/21M1412268 (2022).
20
-
21
- Parameters
22
- ----------
23
- target : `cuqi.distribution.Posterior`
24
- The target posterior distribution to sample.
25
-
26
- initial_point : ndarray, *Optional*
27
- Initial parameters.
28
- If not provided, it defaults to zeros.
29
-
30
- maxit : int
31
- Maximum number of inner iterations for solver when generating one sample.
32
- If not provided, it defaults to 50.
33
-
34
- tol : float
35
- Tolerance for inner solver.
36
- The inner solvers will stop before maxit if convergence check reaches tol.
37
- If not provided, it defaults to 1e-4.
38
-
39
- beta : float
40
- Smoothing parameter for the Gaussian approximation of the Laplace distribution.
41
- A small value in the range of 1e-7 to 1e-3 is recommended, though values out of this
42
- range might give better results in some cases. Generally, a larger beta value makes
43
- sampling easier but results in a worse approximation. See details in Section 3.3 of the paper.
44
- If not provided, it defaults to 1e-5.
45
-
46
- callback : callable, optional
47
- A function that will be called after each sampling step. It can be useful for monitoring the sampler during sampling.
48
- The function should take three arguments: the sampler object, the index of the current sampling step, the total number of requested samples. The last two arguments are integers. An example of the callback function signature is: `callback(sampler, sample_index, num_of_samples)`.
49
- """
50
- def __init__(self, target=None, initial_point=None, maxit=50, tol=1e-4, beta=1e-5, **kwargs):
51
-
52
- super().__init__(target=target, initial_point=initial_point, **kwargs)
53
-
54
- # Parameters
55
- self.maxit = maxit
56
- self.tol = tol
57
- self.beta = beta
58
-
59
- def _initialize(self):
60
- self._precompute()
61
-
62
- @property
63
- def prior(self):
64
- return self.target.prior
65
-
66
- @property
67
- def likelihood(self):
68
- return self.target.likelihood
69
-
70
- @property
71
- def model(self):
72
- return self.target.model
73
-
74
- @property
75
- def _data(self):
76
- return self.target.data - self.target.model._shift
77
-
78
- def _precompute(self):
79
-
80
- D = self.prior._diff_op
81
- n = D.shape[0]
82
-
83
- # Gaussian approximation of LMRF prior as function of x_k
84
- def Lk_fun(x_k):
85
- dd = 1/np.sqrt((D @ x_k)**2 + self.beta*np.ones(n))
86
- W = sp.sparse.diags(dd)
87
- return W.sqrt() @ D
88
- self.Lk_fun = Lk_fun
89
-
90
- self._m = len(self._data)
91
- self._L1 = self.likelihood.distribution.sqrtprec
92
-
93
- # If prior location is scalar, repeat it to match dimensions
94
- if len(self.prior.location) == 1:
95
- self._priorloc = np.repeat(self.prior.location, self.dim)
96
- else:
97
- self._priorloc = self.prior.location
98
-
99
- # Initial Laplace approx
100
- self._L2 = Lk_fun(self.initial_point)
101
- self._L2mu = self._L2@self._priorloc
102
- self._b_tild = np.hstack([self._L1@self._data, self._L2mu])
103
-
104
- # Least squares form
105
- def M(x, flag):
106
- if flag == 1:
107
- out1 = self._L1 @ self.model._forward_func_no_shift(x) # Use forward function which excludes shift
108
- out2 = np.sqrt(1/self.prior.scale)*(self._L2 @ x)
109
- out = np.hstack([out1, out2])
110
- elif flag == 2:
111
- idx = int(self._m)
112
- out1 = self.model._adjoint_func_no_shift(self._L1.T@x[:idx])
113
- out2 = np.sqrt(1/self.prior.scale)*(self._L2.T @ x[idx:])
114
- out = out1 + out2
115
- return out
116
- self.M = M
117
-
118
- def step(self):
119
- # Update Laplace approximation
120
- self._L2 = self.Lk_fun(self.current_point)
121
- self._L2mu = self._L2@self._priorloc
122
- self._b_tild = np.hstack([self._L1@self._data, self._L2mu])
123
-
124
- # Sample from approximate posterior
125
- e = np.random.randn(len(self._b_tild))
126
- y = self._b_tild + e # Perturb data
127
- sim = CGLS(self.M, y, self.current_point, self.maxit, self.tol)
128
- self.current_point, _ = sim.solve()
129
- acc = 1
130
- return acc
131
-
132
- def tune(self, skip_len, update_count):
133
- pass
134
-
135
- def validate_target(self):
136
- # Check target type
137
- if not isinstance(self.target, cuqi.distribution.Posterior):
138
- raise ValueError(f"To initialize an object of type {self.__class__}, 'target' need to be of type 'cuqi.distribution.Posterior'.")
139
-
140
- # Check Affine model
141
- if not isinstance(self.likelihood.model, cuqi.model.AffineModel):
142
- raise TypeError("Model needs to be affine or linear")
143
-
144
- # Check Gaussian likelihood
145
- if not hasattr(self.likelihood.distribution, "sqrtprec"):
146
- raise TypeError("Distribution in Likelihood must contain a sqrtprec attribute")
147
-
148
- # Check that prior is LMRF
149
- if not isinstance(self.prior, cuqi.distribution.LMRF):
150
- raise ValueError('Unadjusted Gaussian Laplace approximation (UGLA) requires LMRF prior')
151
-
152
- def _get_default_initial_point(self, dim):
153
- """ Get the default initial point for the sampler. Defaults to an array of zeros. """
154
- return np.zeros(dim)
@@ -1,80 +0,0 @@
1
- import numpy as np
2
- import cuqi
3
- from cuqi.experimental.mcmc import ProposalBasedSampler
4
-
5
-
6
- class MH(ProposalBasedSampler):
7
- """ Metropolis-Hastings (MH) sampler.
8
-
9
- Parameters
10
- ----------
11
- target : cuqi.density.Density
12
- Target density or distribution.
13
-
14
- proposal : cuqi.distribution.Distribution or callable
15
- Proposal distribution. If None, a random walk MH is used (i.e., Gaussian proposal with identity covariance).
16
-
17
- scale : float
18
- Scaling parameter for the proposal distribution.
19
-
20
- kwargs : dict
21
- Additional keyword arguments to be passed to the base class :class:`ProposalBasedSampler`.
22
-
23
- """
24
-
25
- _STATE_KEYS = ProposalBasedSampler._STATE_KEYS.union({'scale', '_scale_temp'})
26
-
27
- def __init__(self, target=None, proposal=None, scale=1, **kwargs):
28
- super().__init__(target, proposal=proposal, scale=scale, **kwargs)
29
-
30
- def _initialize(self):
31
- # Due to a bug? in old MH, we must keep track of this extra variable to match behavior.
32
- self._scale_temp = self.scale
33
-
34
- def validate_target(self):
35
- # Fail only when there is no log density, which is currently assumed to be the case in case NaN is returned.
36
- if np.isnan(self.target.logd(self._get_default_initial_point(self.dim))):
37
- raise ValueError("Target does not have valid logd")
38
-
39
- def validate_proposal(self):
40
- if not isinstance(self.proposal, cuqi.distribution.Distribution):
41
- raise ValueError("Proposal must be a cuqi.distribution.Distribution object")
42
- if not self.proposal.is_symmetric:
43
- raise ValueError("Proposal must be symmetric")
44
-
45
- def step(self):
46
- # propose state
47
- xi = self.proposal.sample(1) # sample from the proposal
48
- x_star = self.current_point + self.scale*xi.flatten() # MH proposal
49
-
50
- # evaluate target
51
- target_eval_star = self.target.logd(x_star)
52
-
53
- # ratio and acceptance probability
54
- ratio = target_eval_star - self.current_target_logd # proposal is symmetric
55
- alpha = min(0, ratio)
56
-
57
- # accept/reject
58
- u_theta = np.log(np.random.rand())
59
- acc = 0
60
- if (u_theta <= alpha) and \
61
- (not np.isnan(target_eval_star)) and \
62
- (not np.isinf(target_eval_star)):
63
- self.current_point = x_star
64
- self.current_target_logd = target_eval_star
65
- acc = 1
66
-
67
- return acc
68
-
69
- def tune(self, skip_len, update_count):
70
- hat_acc = np.mean(self._acc[-skip_len:])
71
-
72
- # d. compute new scaling parameter
73
- zeta = 1/np.sqrt(update_count+1) # ensures that the variation of lambda(i) vanishes
74
-
75
- # We use self._scale_temp here instead of self.scale in update. This might be a bug,
76
- # but is equivalent to old MH
77
- self._scale_temp = np.exp(np.log(self._scale_temp) + zeta*(hat_acc-0.234))
78
-
79
- # update parameters
80
- self.scale = min(self._scale_temp, 1)