CUQIpy 1.1.1.post0.dev36__py3-none-any.whl → 1.4.1.post0.dev124__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.

Files changed (92) hide show
  1. cuqi/__init__.py +2 -0
  2. cuqi/_version.py +3 -3
  3. cuqi/algebra/__init__.py +2 -0
  4. cuqi/algebra/_abstract_syntax_tree.py +358 -0
  5. cuqi/algebra/_ordered_set.py +82 -0
  6. cuqi/algebra/_random_variable.py +457 -0
  7. cuqi/array/_array.py +4 -13
  8. cuqi/config.py +7 -0
  9. cuqi/density/_density.py +9 -1
  10. cuqi/distribution/__init__.py +3 -2
  11. cuqi/distribution/_beta.py +7 -11
  12. cuqi/distribution/_cauchy.py +2 -2
  13. cuqi/distribution/_custom.py +0 -6
  14. cuqi/distribution/_distribution.py +31 -45
  15. cuqi/distribution/_gamma.py +7 -3
  16. cuqi/distribution/_gaussian.py +2 -12
  17. cuqi/distribution/_inverse_gamma.py +4 -10
  18. cuqi/distribution/_joint_distribution.py +112 -15
  19. cuqi/distribution/_lognormal.py +0 -7
  20. cuqi/distribution/{_modifiedhalfnormal.py → _modified_half_normal.py} +23 -23
  21. cuqi/distribution/_normal.py +34 -7
  22. cuqi/distribution/_posterior.py +9 -0
  23. cuqi/distribution/_truncated_normal.py +129 -0
  24. cuqi/distribution/_uniform.py +47 -1
  25. cuqi/experimental/__init__.py +2 -2
  26. cuqi/experimental/_recommender.py +216 -0
  27. cuqi/geometry/__init__.py +2 -0
  28. cuqi/geometry/_geometry.py +15 -1
  29. cuqi/geometry/_product_geometry.py +181 -0
  30. cuqi/implicitprior/__init__.py +5 -3
  31. cuqi/implicitprior/_regularized_gaussian.py +483 -0
  32. cuqi/implicitprior/{_regularizedGMRF.py → _regularized_gmrf.py} +4 -2
  33. cuqi/implicitprior/{_regularizedUnboundedUniform.py → _regularized_unbounded_uniform.py} +3 -2
  34. cuqi/implicitprior/_restorator.py +269 -0
  35. cuqi/legacy/__init__.py +2 -0
  36. cuqi/{experimental/mcmc → legacy/sampler}/__init__.py +7 -11
  37. cuqi/legacy/sampler/_conjugate.py +55 -0
  38. cuqi/legacy/sampler/_conjugate_approx.py +52 -0
  39. cuqi/legacy/sampler/_cwmh.py +196 -0
  40. cuqi/legacy/sampler/_gibbs.py +231 -0
  41. cuqi/legacy/sampler/_hmc.py +335 -0
  42. cuqi/{experimental/mcmc → legacy/sampler}/_langevin_algorithm.py +82 -111
  43. cuqi/legacy/sampler/_laplace_approximation.py +184 -0
  44. cuqi/legacy/sampler/_mh.py +190 -0
  45. cuqi/legacy/sampler/_pcn.py +244 -0
  46. cuqi/{experimental/mcmc → legacy/sampler}/_rto.py +132 -90
  47. cuqi/legacy/sampler/_sampler.py +182 -0
  48. cuqi/likelihood/_likelihood.py +9 -1
  49. cuqi/model/__init__.py +1 -1
  50. cuqi/model/_model.py +1361 -359
  51. cuqi/pde/__init__.py +4 -0
  52. cuqi/pde/_observation_map.py +36 -0
  53. cuqi/pde/_pde.py +134 -33
  54. cuqi/problem/_problem.py +93 -87
  55. cuqi/sampler/__init__.py +120 -8
  56. cuqi/sampler/_conjugate.py +376 -35
  57. cuqi/sampler/_conjugate_approx.py +40 -16
  58. cuqi/sampler/_cwmh.py +132 -138
  59. cuqi/{experimental/mcmc → sampler}/_direct.py +1 -1
  60. cuqi/sampler/_gibbs.py +288 -130
  61. cuqi/sampler/_hmc.py +328 -201
  62. cuqi/sampler/_langevin_algorithm.py +284 -100
  63. cuqi/sampler/_laplace_approximation.py +87 -117
  64. cuqi/sampler/_mh.py +47 -157
  65. cuqi/sampler/_pcn.py +65 -213
  66. cuqi/sampler/_rto.py +211 -142
  67. cuqi/sampler/_sampler.py +553 -136
  68. cuqi/samples/__init__.py +1 -1
  69. cuqi/samples/_samples.py +24 -18
  70. cuqi/solver/__init__.py +6 -4
  71. cuqi/solver/_solver.py +230 -26
  72. cuqi/testproblem/_testproblem.py +2 -3
  73. cuqi/utilities/__init__.py +6 -1
  74. cuqi/utilities/_get_python_variable_name.py +2 -2
  75. cuqi/utilities/_utilities.py +182 -2
  76. {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info}/METADATA +10 -6
  77. cuqipy-1.4.1.post0.dev124.dist-info/RECORD +101 -0
  78. {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info}/WHEEL +1 -1
  79. CUQIpy-1.1.1.post0.dev36.dist-info/RECORD +0 -92
  80. cuqi/experimental/mcmc/_conjugate.py +0 -197
  81. cuqi/experimental/mcmc/_conjugate_approx.py +0 -81
  82. cuqi/experimental/mcmc/_cwmh.py +0 -191
  83. cuqi/experimental/mcmc/_gibbs.py +0 -268
  84. cuqi/experimental/mcmc/_hmc.py +0 -470
  85. cuqi/experimental/mcmc/_laplace_approximation.py +0 -156
  86. cuqi/experimental/mcmc/_mh.py +0 -78
  87. cuqi/experimental/mcmc/_pcn.py +0 -89
  88. cuqi/experimental/mcmc/_sampler.py +0 -561
  89. cuqi/experimental/mcmc/_utilities.py +0 -17
  90. cuqi/implicitprior/_regularizedGaussian.py +0 -323
  91. {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info/licenses}/LICENSE +0 -0
  92. {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,483 @@
1
+ from cuqi.utilities import get_non_default_args
2
+ from cuqi.distribution import Distribution, Gaussian
3
+ from cuqi.solver import ProjectNonnegative, ProjectBox, ProximalL1
4
+ from cuqi.geometry import Continuous1D, Continuous2D, Image2D
5
+ from cuqi.operator import FirstOrderFiniteDifference, SecondOrderFiniteDifference, Operator
6
+
7
+ import numpy as np
8
+ import scipy.sparse as sparse
9
+ import scipy.optimize as spoptimize
10
+
11
+ from copy import copy
12
+
13
+
14
+ class RegularizedGaussian(Distribution):
15
+ """ Implicit Regularized Gaussian.
16
+
17
+ Defines a so-called implicit prior based on a Gaussian distribution with implicit regularization.
18
+ The regularization can be defined in the form of a proximal operator or a projector.
19
+ Alternatively, preset constraints and regularization can be used.
20
+
21
+ Precisely one of proximal, projector, constraint or regularization needs to be provided. Otherwise, an error is raised.
22
+
23
+ Can be used as a prior in a posterior which can be sampled with the RegularizedLinearRTO sampler.
24
+
25
+
26
+ For more details on implicit regularized Gaussian see the following paper:
27
+
28
+ [1] Everink, Jasper M., Yiqiu Dong, and Martin S. Andersen. "Sparse Bayesian inference with regularized
29
+ Gaussian distributions." Inverse Problems 39.11 (2023): 115004.
30
+
31
+ Parameters
32
+ ----------
33
+ mean
34
+ See :class:`~cuqi.distribution.Gaussian` for details.
35
+
36
+ cov
37
+ See :class:`~cuqi.distribution.Gaussian` for details.
38
+
39
+ prec
40
+ See :class:`~cuqi.distribution.Gaussian` for details.
41
+
42
+ sqrtcov
43
+ See :class:`~cuqi.distribution.Gaussian` for details.
44
+
45
+ sqrtprec
46
+ See :class:`~cuqi.distribution.Gaussian` for details.
47
+
48
+ proximal : callable f(x, scale), list of tuples (callable proximal operator of f_i, linear operator L_i) or None
49
+ If callable:
50
+ Euclidean proximal operator f of the regularization function g, that is, a solver for the optimization problem
51
+ min_z 0.5||x-z||_2^2+scale*g(x).
52
+ If list of tuples (callable proximal operator of f_i, linear operator L_i):
53
+ Each callable proximal operator of f_i accepts two arguments (x, p) and should return the minimizer of p/2||x-z||^2 + f(x) over z for some f.
54
+ Each linear operator needs to have the '__matmul__', 'T' and 'shape' attributes;
55
+ this includes numpy.ndarray, scipy.sparse.sparray, scipy.sparse.linalg.LinearOperator and cuqi.operator.Operator.
56
+ The corresponding regularization takes the form
57
+ sum_i f_i(L_i x),
58
+ where the sum ranges from 1 to an arbitrary n.
59
+
60
+ projector : callable f(x) or None
61
+ Euclidean projection onto the constraint C, that is, a solver for the optimization problem
62
+ min_(z in C) 0.5||x-z||_2^2.
63
+
64
+ constraint : string or None
65
+ Preset constraints that generate the corresponding proximal parameter. Required for use in Gibbs. For any geometry the following can be chosen:
66
+ - "nonnegativity"
67
+ - "box", the following additional parameters can be passed:
68
+ lower_bound : array_like or None
69
+ Lower bound of box, defaults to zero
70
+ upper_bound : array_like
71
+ Upper bound of box, defaults to one
72
+ Additionally, for Continuous1D geometry the following options can be chosen:
73
+ - "increasing"
74
+ - "decreasing"
75
+ - "convex"
76
+ - "concave"
77
+
78
+ regularization : string or None
79
+ Preset regularization that generate the corresponding proximal parameter. Can be set to "l1" or 'tv'. Required for use in Gibbs in future update.
80
+ For "l1" or "tv", the following additional parameters can be passed:
81
+ strength : scalar
82
+ Regularization parameter, i.e., strength*||Lx||_1, defaults to one
83
+
84
+ """
85
+
86
+ def __init__(self, mean=None, cov=None, prec=None, sqrtcov=None, sqrtprec=None, proximal = None, projector = None, constraint = None, regularization = None, **kwargs):
87
+
88
+ # Store regularization parameters and remove them from kwargs passed to Gaussian
89
+ optional_regularization_parameters = {
90
+ "lower_bound" : kwargs.pop("lower_bound", None), # Takes default of ProjectBox if None
91
+ "upper_bound" : kwargs.pop("upper_bound", None), # Takes default of ProjectBox if None
92
+ "strength" : kwargs.pop("strength", 1)
93
+ }
94
+
95
+ # We init the underlying Gaussian first for geometry and dimensionality handling
96
+ self._gaussian = Gaussian(mean=mean, cov=cov, prec=prec, sqrtcov=sqrtcov, sqrtprec=sqrtprec, **kwargs)
97
+ kwargs.pop("geometry", None)
98
+
99
+ # Init from abstract distribution class
100
+ super().__init__(**kwargs)
101
+
102
+ self._force_list = False
103
+ self._parse_regularization_input_arguments(proximal, projector, constraint, regularization, optional_regularization_parameters)
104
+
105
+ def _parse_regularization_input_arguments(self, proximal, projector, constraint, regularization, optional_regularization_parameters):
106
+ """ Parse regularization input arguments with guarding statements and store internal states """
107
+
108
+ # Guards checking whether the regularization inputs are valid
109
+ if (proximal is not None) + (projector is not None) + max((constraint is not None), (regularization is not None)) == 0:
110
+ raise ValueError("At least some constraint or regularization has to be specified for RegularizedGaussian")
111
+
112
+ if (proximal is not None) + (projector is not None) == 2:
113
+ raise ValueError("Only one of proximal or projector can be used.")
114
+
115
+ if (proximal is not None) + (projector is not None) + max((constraint is not None), (regularization is not None)) > 1:
116
+ raise ValueError("User-defined proximals and projectors cannot be combined with pre-defined constraints and regularization.")
117
+
118
+ # Branch between user-defined and preset
119
+ if (proximal is not None) + (projector is not None) >= 1:
120
+ self._parse_user_specified_input(proximal, projector)
121
+ else:
122
+ # Set constraint and regularization presets for use with Gibbs
123
+ self._preset = {"constraint": None,
124
+ "regularization": None}
125
+
126
+ self._parse_preset_constraint_input(constraint, optional_regularization_parameters)
127
+ self._parse_preset_regularization_input(regularization, optional_regularization_parameters)
128
+
129
+ # Merge
130
+ self._merge_predefined_option()
131
+
132
+ def _parse_user_specified_input(self, proximal, projector):
133
+ # Guard for checking partial validy of proximals or projectors
134
+ if proximal is not None:
135
+ if callable(proximal):
136
+ if len(get_non_default_args(proximal)) != 2:
137
+ raise ValueError("Proximal should take 2 arguments.")
138
+ elif isinstance(proximal, list):
139
+ for val in proximal:
140
+ if len(val) != 2:
141
+ raise ValueError("Each value in the proximal list needs to consistent of two elements: a proximal operator and a linear operator.")
142
+ if callable(val[0]):
143
+ if len(get_non_default_args(val[0])) != 2:
144
+ raise ValueError("Proximal should take 2 arguments.")
145
+ else:
146
+ raise ValueError("Proximal operators need to be callable.")
147
+ if not (hasattr(val[1], '__matmul__') and hasattr(val[1], 'T') and hasattr(val[1], 'shape')):
148
+ raise ValueError("Linear operator not supported, must have '__matmul__', 'T' and 'shape' attributes.")
149
+ else:
150
+ raise ValueError("Proximal needs to be callable or a list. See documentation.")
151
+
152
+ if projector is not None:
153
+ if callable(projector):
154
+ if len(get_non_default_args(projector)) != 1:
155
+ raise ValueError("Projector should take 1 argument.")
156
+ else:
157
+ raise ValueError("Projector needs to be callable")
158
+
159
+ # Set user-defined proximals or projectors
160
+ if proximal is not None:
161
+ self._preset = None
162
+ self._proximal = proximal
163
+ return
164
+
165
+ if projector is not None:
166
+ self._preset = None
167
+ self._proximal = lambda z, gamma: projector(z)
168
+ return
169
+
170
+ def _parse_preset_constraint_input(self, constraint, optional_regularization_parameters):
171
+ # Create data for constraints
172
+ self._constraint_prox = None
173
+ self._constraint_oper = None
174
+ if constraint is not None:
175
+ if not isinstance(constraint, str):
176
+ raise ValueError("Constraint needs to be specified as a string.")
177
+
178
+ c_lower = constraint.lower()
179
+ if c_lower == "nonnegativity":
180
+ self._constraint_prox = lambda z, gamma: ProjectNonnegative(z)
181
+ self._box_bounds = (np.ones(self.dim)*0, np.ones(self.dim)*np.inf)
182
+ self._preset["constraint"] = "nonnegativity"
183
+ elif c_lower == "box":
184
+ _box_lower = optional_regularization_parameters["lower_bound"]
185
+ _box_upper = optional_regularization_parameters["upper_bound"]
186
+ self._proximal = lambda z, _: ProjectBox(z, _box_lower, _box_upper)
187
+ self._box_bounds = (np.ones(self.dim)*_box_lower, np.ones(self.dim)*_box_upper)
188
+ self._preset["constraint"] = "box"
189
+ elif c_lower == "increasing":
190
+ if not isinstance(self.geometry, Continuous1D):
191
+ raise ValueError("Geometry not supported for " + c_lower)
192
+ if hasattr(spoptimize, 'isotonic_regression'):
193
+ self._constraint_prox = lambda z, _: spoptimize.isotonic_regression(z, increasing=True).x
194
+ else:
195
+ raise AttributeError(f"The function 'isotonic_regression' does not exist in scipy.optimize. Installed scipy version: {spoptimize.__version__}. You need to install a scipy >= 1.12.0")
196
+ self._preset["constraint"] = "increasing"
197
+ elif c_lower == "decreasing":
198
+ if not isinstance(self.geometry, Continuous1D):
199
+ raise ValueError("Geometry not supported for " + c_lower)
200
+ if hasattr(spoptimize, 'isotonic_regression'):
201
+ self._constraint_prox = lambda z, _: spoptimize.isotonic_regression(z, increasing=False).x
202
+ else:
203
+ raise AttributeError(f"The function 'isotonic_regression' does not exist in scipy.optimize. Installed scipy version: {spoptimize.__version__}. You need to install a scipy >= 1.12.0")
204
+ self._preset["constraint"] = "decreasing"
205
+ elif c_lower == "convex":
206
+ if not isinstance(self.geometry, Continuous1D):
207
+ raise ValueError("Geometry not supported for " + c_lower)
208
+ self._constraint_prox = lambda z, _: -ProjectNonnegative(-z)
209
+ self._constraint_oper = SecondOrderFiniteDifference(self.geometry.fun_shape, bc_type='neumann')
210
+ self._preset["constraint"] = "convex"
211
+ elif c_lower == "concave":
212
+ if not isinstance(self.geometry, Continuous1D):
213
+ raise ValueError("Geometry not supported for " + c_lower)
214
+ self._constraint_prox = lambda z, _: ProjectNonnegative(z)
215
+ self._constraint_oper = SecondOrderFiniteDifference(self.geometry.fun_shape, bc_type='neumann')
216
+ self._preset["constraint"] = "concave"
217
+ else:
218
+ raise ValueError("Constraint not supported.")
219
+
220
+ def _parse_preset_regularization_input(self, regularization, optional_regularization_parameters):
221
+ # Create data for regularization
222
+ self._regularization_prox = None
223
+ self._regularization_oper = None
224
+ if regularization is not None:
225
+ if not isinstance(regularization, str):
226
+ raise ValueError("Regularization needs to be specified as a string.")
227
+
228
+ self._strength = optional_regularization_parameters["strength"]
229
+ r_lower = regularization.lower()
230
+ if r_lower == "l1":
231
+ self._regularization_prox = lambda z, gamma: ProximalL1(z, gamma*self._strength)
232
+ self._preset["regularization"] = "l1"
233
+ elif r_lower == "tv":
234
+ # Store the transformation to reuse when modifying the strength
235
+ if not isinstance(self.geometry, (Continuous1D, Continuous2D, Image2D)):
236
+ raise ValueError("Geometry not supported for total variation")
237
+ self._regularization_prox = lambda z, gamma: ProximalL1(z, gamma*self._strength)
238
+ self._regularization_oper = FirstOrderFiniteDifference(self.geometry.fun_shape, bc_type='neumann')
239
+ self._preset["regularization"] = "tv"
240
+ else:
241
+ raise ValueError("Regularization not supported.")
242
+
243
+ def _merge_predefined_option(self):
244
+ # Check whether it is a single proximal and hence FISTA could be used in RegularizedLinearRTO
245
+ if ((not self._force_list) and
246
+ ((self._constraint_prox is not None) + (self._regularization_prox is not None) == 1) and
247
+ ((self._constraint_oper is not None) + (self._regularization_oper is not None) == 0)):
248
+ if self._constraint_prox is not None:
249
+ self._proximal = self._constraint_prox
250
+ else:
251
+ self._proximal = self._regularization_prox
252
+ return
253
+
254
+ # Merge regularization choices in list for use in ADMM by RegularizedLinearRTO
255
+ self._proximal = []
256
+ if self._constraint_prox is not None:
257
+ self._proximal += [(self._constraint_prox, self._constraint_oper if self._constraint_oper is not None else sparse.eye(self.geometry.par_dim))]
258
+ if self._regularization_prox is not None:
259
+ self._proximal += [(self._regularization_prox, self._regularization_oper if self._regularization_oper is not None else sparse.eye(self.geometry.par_dim))]
260
+
261
+ @property
262
+ def transformation(self):
263
+ return self._transformation
264
+
265
+ @property
266
+ def strength(self):
267
+ return self._strength
268
+
269
+ @strength.setter
270
+ def strength(self, value):
271
+ if self._preset is None or self._preset["regularization"] is None:
272
+ raise TypeError("Strength is only used when the regularization is set to l1 or TV.")
273
+
274
+ self._strength = value
275
+ if self._preset["regularization"] in ["l1", "tv"]:
276
+ self._regularization_prox = lambda z, gamma: ProximalL1(z, gamma*self._strength)
277
+
278
+ # Create new list of proximals based on updated regularization
279
+ self._merge_predefined_option()
280
+
281
+ # This is a getter only attribute for the underlying Gaussian
282
+ # It also ensures that the name of the underlying Gaussian
283
+ # matches the name of the implicit regularized Gaussian
284
+ @property
285
+ def gaussian(self):
286
+ if self._name is not None:
287
+ self._gaussian._name = self._name
288
+ return self._gaussian
289
+
290
+ @property
291
+ def proximal(self):
292
+ return self._proximal
293
+
294
+ @proximal.setter
295
+ def proximal(self, value):
296
+ if callable(value):
297
+ if len(get_non_default_args(value)) != 2:
298
+ raise ValueError("Proximal should take 2 arguments.")
299
+ elif isinstance(value, list):
300
+ for (prox, op) in value:
301
+ if len(get_non_default_args(prox)) != 2:
302
+ raise ValueError("Proximal should take 2 arguments.")
303
+ if op.shape[1] != self.geometry.par_dim:
304
+ raise ValueError("Incorrect shape of linear operator in proximal list.")
305
+ else:
306
+ raise ValueError("Proximal needs to be callable or a list. See documentation.")
307
+
308
+ self._proximal = value
309
+
310
+ # For all the presets, self._proximal is set directly,
311
+ self._preset = None
312
+
313
+ @property
314
+ def preset(self):
315
+ return self._preset
316
+
317
+ def logpdf(self, x):
318
+ return np.nan
319
+ #raise ValueError(
320
+ # f"The logpdf of a implicit regularized Gaussian is not be defined.")
321
+
322
+ def _sample(self, N, rng=None):
323
+ raise ValueError(
324
+ "Cannot be sampled from.")
325
+
326
+ @staticmethod
327
+ def constraint_options():
328
+ return ["nonnegativity", "box", "increasing", "decreasing", "convex", "concave"]
329
+
330
+ @staticmethod
331
+ def regularization_options():
332
+ return ["l1", "tv"]
333
+
334
+
335
+ # --- Defer behavior of the underlying Gaussian --- #
336
+ @property
337
+ def geometry(self):
338
+ return self.gaussian.geometry
339
+
340
+ @geometry.setter
341
+ def geometry(self, value):
342
+ self.gaussian.geometry = value
343
+
344
+ @property
345
+ def mean(self):
346
+ return self.gaussian.mean
347
+
348
+ @mean.setter
349
+ def mean(self, value):
350
+ self.gaussian.mean = value
351
+
352
+ @property
353
+ def cov(self):
354
+ return self.gaussian.cov
355
+
356
+ @cov.setter
357
+ def cov(self, value):
358
+ self.gaussian.cov = value
359
+
360
+ @property
361
+ def prec(self):
362
+ return self.gaussian.prec
363
+
364
+ @prec.setter
365
+ def prec(self, value):
366
+ self.gaussian.prec = value
367
+
368
+ @property
369
+ def sqrtprec(self):
370
+ return self.gaussian.sqrtprec
371
+
372
+ @sqrtprec.setter
373
+ def sqrtprec(self, value):
374
+ self.gaussian.sqrtprec = value
375
+
376
+ @property
377
+ def sqrtcov(self):
378
+ return self.gaussian.sqrtcov
379
+
380
+ @sqrtcov.setter
381
+ def sqrtcov(self, value):
382
+ self.gaussian.sqrtcov = value
383
+
384
+ def get_mutable_variables(self):
385
+ mutable_vars = self.gaussian.get_mutable_variables().copy()
386
+ if self.preset is not None and self.preset['regularization'] in ["l1", "tv"]:
387
+ mutable_vars += ["strength"]
388
+ return mutable_vars
389
+
390
+ def _make_copy(self):
391
+ """ Returns a shallow copy of the density keeping a pointer to the original. """
392
+ # Using deepcopy would also copy the underlying geometry, which causes a crash because geometries won't match anymore.
393
+ new_density = copy(self)
394
+ new_density._gaussian = copy(new_density._gaussian)
395
+ new_density._original_density = self
396
+ return new_density
397
+
398
+
399
+ class ConstrainedGaussian(RegularizedGaussian):
400
+ """ Implicit Constrained Gaussian.
401
+
402
+ Defines a so-called implicit prior based on a Gaussian distribution with implicit constraints.
403
+ The constraint can be defined as a preset or in the form of a projector.
404
+
405
+ Precisely one of projector or constraint needs to be provided. Otherwise, an error is raised.
406
+
407
+ Can be used as a prior in a posterior which can be sampled with the RegularizedLinearRTO sampler.
408
+
409
+ Alias for :class:`~cuqi.implicitprior.RegularizedGaussian` with only constraints available.
410
+
411
+ For more details on implicit regularized Gaussian see the following paper:
412
+
413
+ [1] Everink, Jasper M., Yiqiu Dong, and Martin S. Andersen. "Sparse Bayesian inference with regularized
414
+ Gaussian distributions." Inverse Problems 39.11 (2023): 115004.
415
+
416
+ Parameters
417
+ ----------
418
+ mean
419
+ See :class:`~cuqi.distribution.Gaussian` for details.
420
+
421
+ cov
422
+ See :class:`~cuqi.distribution.Gaussian` for details.
423
+
424
+ prec
425
+ See :class:`~cuqi.distribution.Gaussian` for details.
426
+
427
+ sqrtcov
428
+ See :class:`~cuqi.distribution.Gaussian` for details.
429
+
430
+ sqrtprec
431
+ See :class:`~cuqi.distribution.Gaussian` for details.
432
+
433
+ projector : callable f(x) or None
434
+ Euclidean projection onto the constraint C, that is, a solver for the optimization problem
435
+ min_(z in C) 0.5||x-z||_2^2.
436
+
437
+ constraint : string or None
438
+ Preset constraints that generate the corresponding proximal parameter. Can be set to "nonnegativity" and "box". Required for use in Gibbs.
439
+ For "box", the following additional parameters can be passed:
440
+ lower_bound : array_like or None
441
+ Lower bound of box, defaults to zero
442
+ upper_bound : array_like
443
+ Upper bound of box, defaults to one
444
+
445
+ """
446
+ def __init__(self, mean=None, cov=None, prec=None, sqrtcov=None,sqrtprec=None, projector=None, constraint=None, **kwargs):
447
+ super().__init__(mean=mean, cov=cov, prec=prec, sqrtcov=sqrtcov, sqrtprec=sqrtprec, projector=projector, constraint=constraint, **kwargs)
448
+
449
+
450
+ class NonnegativeGaussian(RegularizedGaussian):
451
+ """ Implicit Nonnegative Gaussian.
452
+
453
+ Defines a so-called implicit prior based on a Gaussian distribution with implicit nonnegativity constraints.
454
+
455
+ Can be used as a prior in a posterior which can be sampled with the RegularizedLinearRTO sampler.
456
+
457
+ Alias for :class:`~cuqi.implicitprior.RegularizedGaussian` with only nonnegativity constraints.
458
+
459
+ For more details on implicit regularized Gaussian see the following paper:
460
+
461
+ [1] Everink, Jasper M., Yiqiu Dong, and Martin S. Andersen. "Sparse Bayesian inference with regularized
462
+ Gaussian distributions." Inverse Problems 39.11 (2023): 115004.
463
+
464
+ Parameters
465
+ ----------
466
+ mean
467
+ See :class:`~cuqi.distribution.Gaussian` for details.
468
+
469
+ cov
470
+ See :class:`~cuqi.distribution.Gaussian` for details.
471
+
472
+ prec
473
+ See :class:`~cuqi.distribution.Gaussian` for details.
474
+
475
+ sqrtcov
476
+ See :class:`~cuqi.distribution.Gaussian` for details.
477
+
478
+ sqrtprec
479
+ See :class:`~cuqi.distribution.Gaussian` for details.
480
+
481
+ """
482
+ def __init__(self, mean=None, cov=None, prec=None, sqrtcov=None,sqrtprec=None, **kwargs):
483
+ super().__init__(mean=mean, cov=cov, prec=prec, sqrtcov=sqrtcov, sqrtprec=sqrtprec, constraint="nonnegativity", **kwargs)
@@ -50,9 +50,9 @@ class RegularizedGMRF(RegularizedGaussian):
50
50
 
51
51
  regularization : string or None
52
52
  Preset regularization. Can be set to "l1". Required for use in Gibbs in future update.
53
- For "l1", the following additional parameters can be passed:
53
+ For "l1" or "tv", the following additional parameters can be passed:
54
54
  strength : scalar
55
- Regularization parameter, i.e., strength*||x||_1 , defaults to one
55
+ Regularization parameter, i.e., strength*||Lx||_1, defaults to one
56
56
 
57
57
  """
58
58
  def __init__(self, mean=None, prec=None, bc_type='zero', order=1, proximal = None, projector = None, constraint = None, regularization = None, **kwargs):
@@ -63,10 +63,12 @@ class RegularizedGMRF(RegularizedGaussian):
63
63
 
64
64
  # Underlying explicit GMRF
65
65
  self._gaussian = GMRF(mean, prec, bc_type=bc_type, order=order, **kwargs)
66
+ kwargs.pop("geometry", None)
66
67
 
67
68
  # Init from abstract distribution class
68
69
  super(Distribution, self).__init__(**kwargs)
69
70
 
71
+ self._force_list = False
70
72
  self._parse_regularization_input_arguments(proximal, projector, constraint, regularization, args)
71
73
 
72
74
 
@@ -45,9 +45,9 @@ class RegularizedUnboundedUniform(RegularizedGaussian):
45
45
 
46
46
  regularization : string or None
47
47
  Preset regularization. Can be set to "l1". Required for use in Gibbs in future update.
48
- For "l1", the following additional parameters can be passed:
48
+ For "l1" or "tv", the following additional parameters can be passed:
49
49
  strength : scalar
50
- Regularization parameter, i.e., strength*||x||_1 , defaults to one
50
+ Regularization parameter, i.e., strength*||Lx||_1, defaults to one
51
51
 
52
52
  """
53
53
  def __init__(self, geometry, proximal = None, projector = None, constraint = None, regularization = None, **kwargs):
@@ -63,4 +63,5 @@ class RegularizedUnboundedUniform(RegularizedGaussian):
63
63
  # Init from abstract distribution class
64
64
  super(Distribution, self).__init__(**kwargs)
65
65
 
66
+ self._force_list = False
66
67
  self._parse_regularization_input_arguments(proximal, projector, constraint, regularization, args)