CUQIpy 1.2.0.post0.dev446__py3-none-any.whl → 1.2.0.post0.dev501__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.2
2
2
  Name: CUQIpy
3
- Version: 1.2.0.post0.dev446
3
+ Version: 1.2.0.post0.dev501
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=LsGilhl-hBLEn6Glt8S_l0OJzAA1sKit_rui8h-D-p0,488
2
2
  cuqi/_messages.py,sha256=fzEBrZT2kbmfecBBPm7spVu7yHdxGARQB4QzXhJbCJ0,415
3
- cuqi/_version.py,sha256=ukIYDW0D_LdEgCoVq2KQxRE9HDsW1NNxfWZNFz2Syds,510
3
+ cuqi/_version.py,sha256=-wStiLQQiCB-PTnm1EIuY2Bqw9TKX4zIsPlS3OkQHg8,510
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
@@ -28,22 +28,22 @@ cuqi/distribution/_joint_distribution.py,sha256=vadRTOpQh1skAgnf-f2-2e6IMvoH2d8b
28
28
  cuqi/distribution/_laplace.py,sha256=5exLvlzJm2AgfvZ3KUSkjfwlGwwbsktBxP8z0iLMik8,1401
29
29
  cuqi/distribution/_lmrf.py,sha256=rdGoQ-fPe1oW6Z29P-l3woq0NX3_RxUQ2rzm1VzemNM,3290
30
30
  cuqi/distribution/_lognormal.py,sha256=8_hOFQ3iu88ujX8vxmfVEZ0fdmlhTY98PlG5PasPjEg,2612
31
- cuqi/distribution/_modifiedhalfnormal.py,sha256=eCg9YhH-zyX25V5WqdBwQykwG_90lm5Qc2901z7jFUE,7390
31
+ cuqi/distribution/_modifiedhalfnormal.py,sha256=ErULXUFRjbMyCYywaOzfuxtoy-XQmC0McMROo2mTQtc,7313
32
32
  cuqi/distribution/_normal.py,sha256=vhIiAseW09IKh1uy0KUq7RP1IuY7hH5aNM1W_R8Gd_Q,2912
33
33
  cuqi/distribution/_posterior.py,sha256=zAfL0GECxekZ2lBt1W6_LN0U_xskMwK4VNce5xAF7ig,5018
34
34
  cuqi/distribution/_smoothed_laplace.py,sha256=p-1Y23mYA9omwiHGkEuv3T2mwcPAAoNlCr7T8osNkjE,2925
35
35
  cuqi/distribution/_truncated_normal.py,sha256=sZkLYgnkGOyS_3ZxY7iw6L62t-Jh6shzsweRsRepN2k,4240
36
36
  cuqi/distribution/_uniform.py,sha256=KA8yQ6ZS3nQGS4PYJ4hpDg6Eq8EQKQvPsIpYfR8fj2w,1967
37
- cuqi/experimental/__init__.py,sha256=6PFlmAkWuxWhzVrZz2g10tBBDuH5542G02nIRQfQCNg,128
37
+ cuqi/experimental/__init__.py,sha256=bIQ9OroeitHbwgNe3wI_JvzkILK0N25Tt7wpquPoU3w,129
38
38
  cuqi/experimental/algebra/__init__.py,sha256=btRAWG58ZfdtK0afXKOg60AX7d76KMBjlZa4AWBCCgU,81
39
39
  cuqi/experimental/algebra/_ast.py,sha256=PdPz19cJMjvnMx4KEzhn4gvxIZX_UViE33Mbttj_5Xw,9873
40
40
  cuqi/experimental/algebra/_orderedset.py,sha256=fKysh4pmI4xF7Y5Z6O86ABzg20o4uBs-v8jmLBMrdpo,2849
41
- cuqi/experimental/algebra/_randomvariable.py,sha256=1VwJjsF5PPmkchGa7mbNCcAgnt19olkvMeHCRAvEVtk,18911
41
+ cuqi/experimental/algebra/_randomvariable.py,sha256=isbFtIWsWXF-yF5Vb56nLy4MCkQM6akjd-dQau4wfbE,19725
42
42
  cuqi/experimental/geometry/__init__.py,sha256=kgoKegfz3Jhr7fpORB_l55z9zLZRtloTLyXFDh1oF2o,47
43
43
  cuqi/experimental/geometry/_productgeometry.py,sha256=G-hIYnfLiRS5IWD2EPXORNBKNP2zSaCCHAeBlDC_R3I,7177
44
44
  cuqi/experimental/mcmc/__init__.py,sha256=zSqLZmxOqQ-F94C9-gPv7g89TX1XxlrlNm071Eb167I,4487
45
- cuqi/experimental/mcmc/_conjugate.py,sha256=VNPQkGity0mposcqxrx4UIeXm35EvJvZED4p2stffvA,9924
46
- cuqi/experimental/mcmc/_conjugate_approx.py,sha256=uEnY2ea9su5ivcNagyRAwpQP2gBY98sXU7N0y5hTADo,3653
45
+ cuqi/experimental/mcmc/_conjugate.py,sha256=MT_On2figPyYPVwrL19ocRFzfcJIvG2SDr3yRSmCSno,18971
46
+ cuqi/experimental/mcmc/_conjugate_approx.py,sha256=jmxe2FEbO9fwpc8opyjJ2px0oed3dGyj0qDwyHo4aOk,3545
47
47
  cuqi/experimental/mcmc/_cwmh.py,sha256=50v3uZaWhlVnfrEB5-lB_7pn8QoUVBe-xWxKGKbmNHg,7234
48
48
  cuqi/experimental/mcmc/_direct.py,sha256=9pQS_2Qk2-ybt6m8WTfPoKetcxQ00WaTRN85-Z0FrBY,777
49
49
  cuqi/experimental/mcmc/_gibbs.py,sha256=evgxf2tLFLlKB3hN0qz9a9NcZQSES8wdacnn3uNWocQ,12005
@@ -58,9 +58,9 @@ cuqi/experimental/mcmc/_utilities.py,sha256=kUzHbhIS3HYZRbneNBK41IogUYX5dS_bJxqE
58
58
  cuqi/geometry/__init__.py,sha256=Tz1WGzZBY-QGH3c0GiyKm9XHN8MGGcnU6TUHLZkzB3o,842
59
59
  cuqi/geometry/_geometry.py,sha256=tsWMca6E-KEXwr_LhjwP7Lsdi5TWCyu0T956Cj5LEXQ,47091
60
60
  cuqi/implicitprior/__init__.py,sha256=6z3lvw-tWDyjZSpB3pYzvijSMK9Zlf1IYqOVTtMD2h4,309
61
- cuqi/implicitprior/_regularizedGMRF.py,sha256=rr3R2C1aheuu_KD35MureZKfOwY8O1pkVDHvuaFnFFU,6300
62
- cuqi/implicitprior/_regularizedGaussian.py,sha256=btpjKUG1byLSu7S3J8N1MZZBuskCEIdmatMASwQEHtE,15656
63
- cuqi/implicitprior/_regularizedUnboundedUniform.py,sha256=H2fTOSqYTlDiLxQ7Ya6wnpCUIkpO4qKrkTOsOPnBBeU,3483
61
+ cuqi/implicitprior/_regularizedGMRF.py,sha256=Ck1JGo8jTb-Z8zDQl4-shMEB2_T0Az9fBpbxDjwatRU,6308
62
+ cuqi/implicitprior/_regularizedGaussian.py,sha256=QlaloekbKojhdXVdmSEFwq__T15XoKBt3uL75mdi0KU,14935
63
+ cuqi/implicitprior/_regularizedUnboundedUniform.py,sha256=Ez7TuyR3Y9Km4qeqGnUJl5tQ8-G3assAQm_id4yBNlI,3491
64
64
  cuqi/implicitprior/_restorator.py,sha256=Z350XUJEt7N59Qw-SIUaBljQNDJk4Zb0i_KRFrt2DCg,10087
65
65
  cuqi/likelihood/__init__.py,sha256=QXif382iwZ5bT3ZUqmMs_n70JVbbjxbqMrlQYbMn4Zo,1776
66
66
  cuqi/likelihood/_likelihood.py,sha256=PuW8ufRefLt6w40JQWqNnEh3YCLxu4pz0h0PcpT8inc,7075
@@ -71,7 +71,7 @@ cuqi/operator/_operator.py,sha256=yNwPTh7jR07AiKMbMQQ5_54EgirlKFsbq9JN1EODaQI,88
71
71
  cuqi/pde/__init__.py,sha256=NyS_ZYruCvy-Yg24qKlwm3ZIX058kLNQX9bqs-xg4ZM,99
72
72
  cuqi/pde/_pde.py,sha256=WRkOYyIdT_T3aZepRh0aS9C5nBbUZUcHaA80iSRvgoo,12572
73
73
  cuqi/problem/__init__.py,sha256=JxJty4JqHTOqSG6NeTGiXRQ7OLxiRK9jvVq3lXLeIRw,38
74
- cuqi/problem/_problem.py,sha256=XyJ_MZbVFUiiywUM-mrra3tUD_QMRK_kXLj4xVqRgAc,38169
74
+ cuqi/problem/_problem.py,sha256=31ByO279-6hM8PhWjwD5k7i9aBAkk9S1tcgMzxv1PiQ,38256
75
75
  cuqi/sampler/__init__.py,sha256=D-dYa0gFgIwQukP8_VKhPGmlGKXbvVo7YqaET4SdAeQ,382
76
76
  cuqi/sampler/_conjugate.py,sha256=ztmUR3V3qZk9zelKx48ULnmMs_zKTDUfohc256VOIe8,2753
77
77
  cuqi/sampler/_conjugate_approx.py,sha256=xX-X71EgxGnZooOY6CIBhuJTs3dhcKfoLnoFxX3CO2g,1938
@@ -87,14 +87,14 @@ cuqi/sampler/_sampler.py,sha256=TkZ_WAS-5Q43oICa-Elc2gftsRTBd7PEDUMDZ9tTGmU,5712
87
87
  cuqi/samples/__init__.py,sha256=vCs6lVk-pi8RBqa6cIN5wyn6u-K9oEf1Na4k1ZMrYv8,44
88
88
  cuqi/samples/_samples.py,sha256=hUc8OnCF9CTCuDTrGHwwzv3wp8mG_6vsJAFvuQ-x0uA,35832
89
89
  cuqi/solver/__init__.py,sha256=KYgAi_8VoAwljTB3S2I87YnJkRtedskLee7hQp_-zp8,220
90
- cuqi/solver/_solver.py,sha256=hGOFEJ74s0qHvXwt8h0JBdnqE5LGa_yZzNB9cd8zPAs,30661
90
+ cuqi/solver/_solver.py,sha256=_Q47Atv8Ze6eMJzA22s0OzdW4lcDigRhbotnCzmrQWE,30662
91
91
  cuqi/testproblem/__init__.py,sha256=DWTOcyuNHMbhEuuWlY5CkYkNDSAqhvsKmJXBLivyblU,202
92
92
  cuqi/testproblem/_testproblem.py,sha256=x769LwwRdJdzIiZkcQUGb_5-vynNTNALXWKato7sS0Q,52540
93
- cuqi/utilities/__init__.py,sha256=H7xpJe2UinjZftKvE2JuXtTi4DqtkR6uIezStAXwfGg,428
93
+ cuqi/utilities/__init__.py,sha256=RB84VstmFcZgPOz58LKSzOvCVebbeKDcKl9MGk-EwoA,515
94
94
  cuqi/utilities/_get_python_variable_name.py,sha256=wxpCaj9f3ZtBNqlGmmuGiITgBaTsY-r94lUIlK6UAU4,2043
95
- cuqi/utilities/_utilities.py,sha256=Jc4knn80vLoA7kgw9FzXwKVFGaNBOXiA9kgvltZU3Ao,11777
96
- CUQIpy-1.2.0.post0.dev446.dist-info/LICENSE,sha256=kJWRPrtRoQoZGXyyvu50Uc91X6_0XRaVfT0YZssicys,10799
97
- CUQIpy-1.2.0.post0.dev446.dist-info/METADATA,sha256=z-UvrfRd531Ja_erEONzN_pqzmd6u3_zh14VnObY3y0,18529
98
- CUQIpy-1.2.0.post0.dev446.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
99
- CUQIpy-1.2.0.post0.dev446.dist-info/top_level.txt,sha256=AgmgMc6TKfPPqbjV0kvAoCBN334i_Lwwojc7HE3ZwD0,5
100
- CUQIpy-1.2.0.post0.dev446.dist-info/RECORD,,
95
+ cuqi/utilities/_utilities.py,sha256=gc9YAj7wFKzyZTE1H5iI_1Tt4AtjT1g5l1-zxBdH-Co,15281
96
+ CUQIpy-1.2.0.post0.dev501.dist-info/LICENSE,sha256=kJWRPrtRoQoZGXyyvu50Uc91X6_0XRaVfT0YZssicys,10799
97
+ CUQIpy-1.2.0.post0.dev501.dist-info/METADATA,sha256=qAa9TTLdoTmo7L8fOcRrE_9sj6Sfk2z2DpfCY8eitD0,18529
98
+ CUQIpy-1.2.0.post0.dev501.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
99
+ CUQIpy-1.2.0.post0.dev501.dist-info/top_level.txt,sha256=AgmgMc6TKfPPqbjV0kvAoCBN334i_Lwwojc7HE3ZwD0,5
100
+ CUQIpy-1.2.0.post0.dev501.dist-info/RECORD,,
cuqi/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-01-20T14:37:50+0100",
11
+ "date": "2025-01-30T19:07:50+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "04ade2ac5f81fefc462cb802ec723be5f10e0f0a",
15
- "version": "1.2.0.post0.dev446"
14
+ "full-revisionid": "ead079a625cad175f6a0159d46c90d7cdf80bc0f",
15
+ "version": "1.2.0.post0.dev501"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -24,13 +24,13 @@ class ModifiedHalfNormal(Distribution):
24
24
 
25
25
  Parameters
26
26
  ----------
27
- alpha : float
27
+ alpha : float or array_like
28
28
  The polynomial exponent parameter :math:`\\alpha` of the MHN distribution. Must be positive.
29
29
 
30
- beta : float
30
+ beta : float or array_like
31
31
  The quadratic exponential parameter :math:`\\beta` of the MHN distribution. Must be positive.
32
32
 
33
- gamma : float
33
+ gamma : float or array_like
34
34
  The linear exponential parameter :math:`\\gamma` of the MHN distribution.
35
35
 
36
36
  """
@@ -38,9 +38,9 @@ class ModifiedHalfNormal(Distribution):
38
38
  # Init from abstract distribution class
39
39
  super().__init__(is_symmetric=is_symmetric, **kwargs)
40
40
 
41
- self._alpha = alpha
42
- self._beta = beta
43
- self._gamma = gamma
41
+ self.alpha = alpha
42
+ self.beta = beta
43
+ self.gamma = gamma
44
44
 
45
45
  @property
46
46
  def alpha(self):
@@ -48,13 +48,13 @@ class ModifiedHalfNormal(Distribution):
48
48
  return self._alpha
49
49
 
50
50
  @alpha.setter
51
- def shape(self, value):
52
- self._shape = force_ndarray(value, flatten=True)
51
+ def alpha(self, value):
52
+ self._alpha = force_ndarray(value, flatten=True)
53
53
 
54
54
  @property
55
55
  def beta(self):
56
56
  """ The quadratic exponential parameter of the MHN distribution. Must be positive. """
57
- return self._alpha
57
+ return self._beta
58
58
 
59
59
  @beta.setter
60
60
  def beta(self, value):
@@ -63,7 +63,7 @@ class ModifiedHalfNormal(Distribution):
63
63
  @property
64
64
  def gamma(self):
65
65
  """ The linear exponential parameter of the MHN distribution. """
66
- return self._alpha
66
+ return self._gamma
67
67
 
68
68
  @gamma.setter
69
69
  def gamma(self, value):
@@ -78,11 +78,8 @@ class ModifiedHalfNormal(Distribution):
78
78
  return (self.alpha - 1)/val - 2*self.beta*val + self.gamma
79
79
 
80
80
  def _gradient(self, val, *args, **kwargs):
81
- if hasattr(self.alpha, '__iter__'):
82
- return np.array([self._gradient_scalar(v) for v in val])
83
- else:
84
- return np.array([self.dim*[self._gradient_scalar(v)] for v in val])
85
-
81
+ return np.array([self._gradient_scalar(v) for v in val])
82
+
86
83
  def _MHN_sample_gamma_proposal(self, alpha, beta, gamma, rng, delta=None):
87
84
  """
88
85
  Sample from a modified half-normal distribution using a Gamma distribution proposal.
@@ -180,7 +177,7 @@ class ModifiedHalfNormal(Distribution):
180
177
 
181
178
  def _sample(self, N, rng=None):
182
179
  if hasattr(self.alpha, '__getitem__'):
183
- return np.array([self._MHN_sample(self.alpha[i], self.beta[i], self.gamma[i], rng=rng) for i in range(N)])
180
+ return np.array([[self._MHN_sample(self.alpha[i], self.beta[i], self.gamma[i], rng=rng) for i in range(len(self.alpha))] for _ in range(N)])
184
181
  else:
185
182
  return np.array([self._MHN_sample(self.alpha, self.beta, self.gamma, rng=rng) for i in range(N)])
186
183
 
@@ -1,4 +1,4 @@
1
- """ Experimental module for testing new features and ideas. """
1
+ """ Experimental module for testing new features and ideas. """
2
2
  from . import mcmc
3
3
  from . import algebra
4
4
  from . import geometry
@@ -6,6 +6,8 @@ import operator
6
6
  import cuqi
7
7
  from cuqi.distribution import Distribution
8
8
  from copy import copy, deepcopy
9
+ import numpy as np
10
+
9
11
 
10
12
  class RandomVariable:
11
13
  """ Random variable defined by a distribution with the option to apply algebraic operations on it.
@@ -165,6 +167,30 @@ class RandomVariable:
165
167
  raise ValueError(f"Expected arguments {self.parameter_names}, got arguments {kwargs}")
166
168
 
167
169
  return self.tree(**kwargs)
170
+
171
+ def sample(self, N=1):
172
+ """ Sample from the random variable.
173
+
174
+ Parameters
175
+ ----------
176
+ N : int, optional
177
+ Number of samples to draw. Default is 1.
178
+ """
179
+
180
+ if self.is_cond:
181
+ raise NotImplementedError(
182
+ "Unable to directly sample from a random variable that has distributions with "
183
+ "conditioning variables. This is not implemented."
184
+ )
185
+
186
+ if N == 1: return self(**{dist.name: dist.sample() for dist in self.distributions})
187
+
188
+ samples = np.array([
189
+ self(**{dist.name: dist.sample() for dist in self.distributions})
190
+ for _ in range(N)
191
+ ]).reshape(-1, N) # Ensure correct shape (dim, N)
192
+
193
+ return cuqi.samples.Samples(samples)
168
194
 
169
195
  @property
170
196
  def tree(self):
@@ -2,9 +2,10 @@ import numpy as np
2
2
  from abc import ABC, abstractmethod
3
3
  import math
4
4
  from cuqi.experimental.mcmc import Sampler
5
- from cuqi.distribution import Posterior, Gaussian, Gamma, GMRF
6
- from cuqi.implicitprior import RegularizedGaussian, RegularizedGMRF
7
- from cuqi.utilities import get_non_default_args
5
+ from cuqi.distribution import Posterior, Gaussian, Gamma, GMRF, ModifiedHalfNormal
6
+ from cuqi.implicitprior import RegularizedGaussian, RegularizedGMRF, RegularizedUnboundedUniform
7
+ from cuqi.utilities import get_non_default_args, count_nonzero, count_constant_components_1D, count_constant_components_2D
8
+ from cuqi.geometry import Continuous1D, Continuous2D, Image2D
8
9
 
9
10
  class Conjugate(Sampler):
10
11
  """ Conjugate sampler
@@ -14,10 +15,12 @@ class Conjugate(Sampler):
14
15
  Currently supported conjugate pairs are:
15
16
  - (Gaussian, Gamma) where Gamma is defined on the precision parameter of the Gaussian
16
17
  - (GMRF, Gamma) where Gamma is defined on the precision parameter of the GMRF
17
- - (RegularizedGaussian, Gamma) with nonnegativity constraints only and Gamma is defined on the precision parameter of the RegularizedGaussian
18
- - (RegularizedGMRF, Gamma) with nonnegativity constraints only and Gamma is defined on the precision parameter of the RegularizedGMRF
18
+ - (RegularizedGaussian, Gamma) with preset constraints only and Gamma is defined on the precision parameter of the RegularizedGaussian
19
+ - (RegularizedGMRF, Gamma) with preset constraints only and Gamma is defined on the precision parameter of the RegularizedGMRF
20
+ - (RegularizedGaussian, ModifiedHalfNormal) with preset constraints and regularization only
21
+ - (RegularizedGMRF, ModifiedHalfNormal) with preset constraints and regularization only
19
22
 
20
- Currently the Gamma distribution must be univariate.
23
+ Currently the Gamma and ModifiedHalfNormal distribution must be univariate.
21
24
 
22
25
  A conjugate pair defines implicitly a so-called conjugate distribution which can be sampled from directly.
23
26
 
@@ -25,9 +28,10 @@ class Conjugate(Sampler):
25
28
 
26
29
  For more information on conjugacy and conjugate distributions see https://en.wikipedia.org/wiki/Conjugate_prior.
27
30
 
28
- For implicit regularized Gaussians see:
31
+ For implicit regularized Gaussians and the corresponding conjugacy relations, see:
29
32
 
30
- [1] Everink, Jasper M., Yiqiu Dong, and Martin S. Andersen. "Bayesian inference with projected densities." SIAM/ASA Journal on Uncertainty Quantification 11.3 (2023): 1025-1043.
33
+ Section 3.3 from [1] Everink, Jasper M., Yiqiu Dong, and Martin S. Andersen. "Bayesian inference with projected densities." SIAM/ASA Journal on Uncertainty Quantification 11.3 (2023): 1025-1043.
34
+ Section 4 from [2] Everink, Jasper M., Yiqiu Dong, and Martin S. Andersen. "Sparse Bayesian inference with regularized Gaussian distributions." Inverse Problems 39.11 (2023): 115004.
31
35
 
32
36
  """
33
37
 
@@ -63,11 +67,19 @@ class Conjugate(Sampler):
63
67
  self._ensure_target_is_posterior()
64
68
  if isinstance(self.target.likelihood.distribution, (Gaussian, GMRF)) and isinstance(self.target.prior, Gamma):
65
69
  self._conjugatepair = _GaussianGammaPair(self.target)
70
+ elif isinstance(self.target.likelihood.distribution, RegularizedUnboundedUniform) and isinstance(self.target.prior, Gamma):
71
+ # Check RegularizedUnboundedUniform before RegularizedGaussian and RegularizedGMRF due to the first inheriting from the second.
72
+ self._conjugatepair = _RegularizedUnboundedUniformGammaPair(self.target)
66
73
  elif isinstance(self.target.likelihood.distribution, (RegularizedGaussian, RegularizedGMRF)) and isinstance(self.target.prior, Gamma):
67
74
  self._conjugatepair = _RegularizedGaussianGammaPair(self.target)
75
+ elif isinstance(self.target.likelihood.distribution, (RegularizedGaussian, RegularizedGMRF)) and isinstance(self.target.prior, ModifiedHalfNormal):
76
+ self._conjugatepair = _RegularizedGaussianModifiedHalfNormalPair(self.target)
68
77
  else:
69
78
  raise ValueError(f"Conjugacy is not defined for likelihood {type(self.target.likelihood.distribution)} and prior {type(self.target.prior)}, in CUQIpy")
70
79
 
80
+ def conjugate_distribution(self):
81
+ return self._conjugatepair.conjugate_distribution()
82
+
71
83
  def __repr__(self):
72
84
  msg = super().__repr__()
73
85
  if hasattr(self, "_conjugatepair"):
@@ -86,35 +98,36 @@ class _ConjugatePair(ABC):
86
98
  pass
87
99
 
88
100
  @abstractmethod
101
+ def conjugate_distribution(self):
102
+ """ Returns the posterior distribution in the form of a CUQIpy distribution """
103
+ pass
104
+
89
105
  def sample(self):
90
106
  """ Sample from the conjugate distribution. """
91
- pass
107
+ return self.conjugate_distribution().sample()
92
108
 
93
109
 
94
110
  class _GaussianGammaPair(_ConjugatePair):
95
111
  """ Implementation for the Gaussian-Gamma conjugate pair."""
96
112
 
97
113
  def validate_target(self):
98
- if not isinstance(self.target.likelihood.distribution, (Gaussian, GMRF)):
99
- raise ValueError("Conjugate sampler only works with a Gaussian likelihood function")
100
-
101
- if not isinstance(self.target.prior, Gamma):
102
- raise ValueError("Conjugate sampler only works with Gamma prior")
103
-
104
114
  if self.target.prior.dim != 1:
105
- raise ValueError("Conjugate sampler only works with univariate Gamma prior")
106
-
107
- key, value = _get_conjugate_parameter(self.target)
108
- if key == "cov":
109
- if not _check_conjugate_parameter_is_scalar_reciprocal(value):
110
- raise ValueError("Gaussian-Gamma conjugate pair defined via covariance requires `cov` for the `Gaussian` to be: lambda x : 1.0/x for the conjugate parameter")
111
- elif key == "prec":
112
- if not _check_conjugate_parameter_is_scalar_identity(value):
113
- raise ValueError("Gaussian-Gamma conjugate pair defined via precision requires `prec` for the `Gaussian` to be: lambda x : x for the conjugate parameter")
114
- else:
115
- raise ValueError("Conjugate sampler for Gaussian likelihood functions only works when conjugate parameter is defined via covariance or precision")
116
-
117
- def sample(self):
115
+ raise ValueError("Gaussian-Gamma conjugacy only works with univariate Gamma prior")
116
+
117
+ key_value_pairs = _get_conjugate_parameter(self.target)
118
+ if len(key_value_pairs) != 1:
119
+ raise ValueError(f"Multiple references to conjugate parameter {self.target.prior.name} found in likelihood. Only one occurance is supported.")
120
+ for key, value in key_value_pairs:
121
+ if key == "cov":
122
+ if not _check_conjugate_parameter_is_scalar_linear_reciprocal(value):
123
+ raise ValueError("Gaussian-Gamma conjugate pair defined via covariance requires cov: lambda x : s/x for the conjugate parameter")
124
+ elif key == "prec":
125
+ if not _check_conjugate_parameter_is_scalar_linear(value):
126
+ raise ValueError("Gaussian-Gamma conjugate pair defined via precision requires prec: lambda x : s*x for the conjugate parameter")
127
+ else:
128
+ raise ValueError(f"RegularizedGaussian-ModifiedHalfNormal conjugacy does not support the conjugate parameter {self.target.prior.name} in the {key} attribute. Only cov and prec")
129
+
130
+ def conjugate_distribution(self):
118
131
  # Extract variables
119
132
  b = self.target.likelihood.data # mu
120
133
  m = len(b) # n
@@ -124,38 +137,33 @@ class _GaussianGammaPair(_ConjugatePair):
124
137
  beta = self.target.prior.rate # beta
125
138
 
126
139
  # Create Gamma distribution and sample
127
- dist = Gamma(shape=m/2 + alpha, rate=.5 * np.linalg.norm(L @ (Ax - b))**2 + beta)
128
-
129
- return dist.sample()
140
+ return Gamma(shape=m/2 + alpha, rate=.5 * np.linalg.norm(L @ (Ax - b))**2 + beta)
130
141
 
131
142
 
132
143
  class _RegularizedGaussianGammaPair(_ConjugatePair):
133
- """Implementation for the Regularized Gaussian-Gamma conjugate pair."""
144
+ """Implementation for the Regularized Gaussian-Gamma conjugate pair using the conjugacy rules from [1], Section 3.3."""
134
145
 
135
146
  def validate_target(self):
136
- if not isinstance(self.target.likelihood.distribution, (RegularizedGaussian, RegularizedGMRF)):
137
- raise ValueError("Conjugate sampler only works with a Regularized Gaussian likelihood function")
138
-
139
- if not isinstance(self.target.prior, Gamma):
140
- raise ValueError("Conjugate sampler only works with Gamma prior")
141
-
142
147
  if self.target.prior.dim != 1:
143
- raise ValueError("Conjugate sampler only works with univariate Gamma prior")
148
+ raise ValueError("RegularizedGaussian-Gamma conjugacy only works with univariate ModifiedHalfNormal prior")
144
149
 
145
150
  if self.target.likelihood.distribution.preset not in ["nonnegativity"]:
146
- raise ValueError("Conjugate sampler only works with implicit regularized Gaussian likelihood with nonnegativity constraints")
147
-
148
- key, value = _get_conjugate_parameter(self.target)
149
- if key == "cov":
150
- if not _check_conjugate_parameter_is_scalar_reciprocal(value):
151
- raise ValueError("Regularized Gaussian-Gamma conjugate pair defined via covariance requires cov: lambda x : 1.0/x for the conjugate parameter")
152
- elif key == "prec":
153
- if not _check_conjugate_parameter_is_scalar_identity(value):
154
- raise ValueError("Regularized Gaussian-Gamma conjugate pair defined via precision requires prec: lambda x : x for the conjugate parameter")
155
- else:
156
- raise ValueError("Conjugate sampler for a Regularized Gaussian likelihood functions only works when conjugate parameter is defined via covariance or precision")
157
-
158
- def sample(self):
151
+ raise ValueError("RegularizedGaussian-Gamma conjugacy only works with implicit regularized Gaussian likelihood with nonnegativity constraints")
152
+
153
+ key_value_pairs = _get_conjugate_parameter(self.target)
154
+ if len(key_value_pairs) != 1:
155
+ raise ValueError(f"Multiple references to conjugate parameter {self.target.prior.name} found in likelihood. Only one occurance is supported.")
156
+ for key, value in key_value_pairs:
157
+ if key == "cov":
158
+ if not _check_conjugate_parameter_is_scalar_linear_reciprocal(value):
159
+ raise ValueError("Regularized Gaussian-Gamma conjugacy defined via covariance requires cov: lambda x : s/x for the conjugate parameter")
160
+ elif key == "prec":
161
+ if not _check_conjugate_parameter_is_scalar_linear(value):
162
+ raise ValueError("Regularized Gaussian-Gamma conjugacy defined via precision requires prec: lambda x : s*x for the conjugate parameter")
163
+ else:
164
+ raise ValueError(f"RegularizedGaussian-ModifiedHalfNormal conjugacy does not support the conjugate parameter {self.target.prior.name} in the {key} attribute. Only cov and prec")
165
+
166
+ def conjugate_distribution(self):
159
167
  # Extract variables
160
168
  b = self.target.likelihood.data # mu
161
169
  m = np.count_nonzero(b) # n
@@ -165,9 +173,107 @@ class _RegularizedGaussianGammaPair(_ConjugatePair):
165
173
  beta = self.target.prior.rate # beta
166
174
 
167
175
  # Create Gamma distribution and sample
168
- dist = Gamma(shape=m/2 + alpha, rate=.5 * np.linalg.norm(L @ (Ax - b))**2 + beta)
176
+ return Gamma(shape=m/2 + alpha, rate=.5 * np.linalg.norm(L @ (Ax - b))**2 + beta)
177
+
178
+
179
+ class _RegularizedUnboundedUniformGammaPair(_ConjugatePair):
180
+ """Implementation for the RegularizedUnboundedUniform-ModifiedHalfNormal conjugate pair using the conjugacy rules from [2], Section 4."""
169
181
 
170
- return dist.sample()
182
+ def validate_target(self):
183
+ if self.target.prior.dim != 1:
184
+ raise ValueError("RegularizedUnboundedUniform-Gamma conjugacy only works with univariate Gamma prior")
185
+
186
+ if self.target.likelihood.distribution.preset not in ["l1", "tv"]:
187
+ raise ValueError("RegularizedUnboundedUniform-Gamma conjugacy only works with implicit regularized Gaussian likelihood with l1 or tv regularization")
188
+
189
+ key_value_pairs = _get_conjugate_parameter(self.target)
190
+ if len(key_value_pairs) != 1:
191
+ raise ValueError(f"Multiple references to conjugate parameter {self.target.prior.name} found in likelihood. Only one occurance is supported.")
192
+ for key, value in key_value_pairs:
193
+ if key == "strength":
194
+ if not _check_conjugate_parameter_is_scalar_linear(value):
195
+ raise ValueError("RegularizedUnboundedUniform-Gamma conjugacy defined via strength requires strength: lambda x : s*x for the conjugate parameter")
196
+ else:
197
+ raise ValueError(f"RegularizedUnboundedUniform-Gamma conjugacy does not support the conjugate parameter {self.target.prior.name} in the {key} attribute. Only strength is supported")
198
+
199
+ def conjugate_distribution(self):
200
+ # Extract prior variables
201
+ alpha = self.target.prior.shape
202
+ beta = self.target.prior.rate
203
+
204
+ # Compute likelihood quantities
205
+ x = self.target.likelihood.data
206
+ if self.target.likelihood.distribution.preset == "l1":
207
+ m = count_nonzero(x)
208
+ elif self.target.likelihood.distribution.preset == "tv" and isinstance(self.target.likelihood.distribution.geometry, Continuous1D):
209
+ m = count_constant_components_1D(x)
210
+ elif self.target.likelihood.distribution.preset == "tv" and isinstance(self.target.likelihood.distribution.geometry, (Continuous2D, Image2D)):
211
+ m = count_constant_components_2D(self.target.likelihood.distribution.geometry.par2fun(x))
212
+
213
+ reg_op = self.target.likelihood.distribution._regularization_oper
214
+ reg_strength = self.target.likelihood.distribution(np.array([1])).strength
215
+ fx = reg_strength*np.linalg.norm(reg_op@x, ord = 1)
216
+
217
+ # Create Gamma distribution
218
+ return Gamma(shape=m/2 + alpha, rate=fx + beta)
219
+
220
+ class _RegularizedGaussianModifiedHalfNormalPair(_ConjugatePair):
221
+ """Implementation for the Regularized Gaussian-ModifiedHalfNormal conjugate pair using the conjugacy rules from [2], Section 4."""
222
+
223
+ def validate_target(self):
224
+ if self.target.prior.dim != 1:
225
+ raise ValueError("RegularizedGaussian-ModifiedHalfNormal conjugacy only works with univariate ModifiedHalfNormal prior")
226
+
227
+ if self.target.likelihood.distribution.preset not in ["l1", "tv"]:
228
+ raise ValueError("RegularizedGaussian-ModifiedHalfNormal conjugacy only works with implicit regularized Gaussian likelihood with l1 or tv regularization")
229
+
230
+ key_value_pairs = _get_conjugate_parameter(self.target)
231
+ if len(key_value_pairs) != 2:
232
+ raise ValueError(f"Incorrect number of references to conjugate parameter {self.target.prior.name} found in likelihood. Found {len(key_value_pairs)} times, but needs to occur in prec or cov, and in strength")
233
+ for key, value in key_value_pairs:
234
+ if key == "strength":
235
+ if not _check_conjugate_parameter_is_scalar_linear(value):
236
+ raise ValueError("RegularizedGaussian-ModifiedHalfNormal conjugacy defined via strength requires strength: lambda x : s*x for the conjugate parameter")
237
+ elif key == "prec":
238
+ if not _check_conjugate_parameter_is_scalar_quadratic(value):
239
+ raise ValueError("RegularizedGaussian-ModifiedHalfNormal conjugacy defined via precision requires prec: lambda x : s*x for the conjugate parameter")
240
+ elif key == "cov":
241
+ if not _check_conjugate_parameter_is_scalar_quadratic_reciprocal(value):
242
+ raise ValueError("RegularizedGaussian-ModifiedHalfNormal conjugacy defined via covariance requires cov: lambda x : s/x for the conjugate parameter")
243
+ else:
244
+ raise ValueError(f"RegularizedGaussian-ModifiedHalfNormal conjugacy does not support the conjugate parameter {self.target.prior.name} in the {key} attribute. Only cov, prec and strength are supported")
245
+
246
+
247
+ def conjugate_distribution(self):
248
+ # Extract prior variables
249
+ alpha = self.target.prior.alpha
250
+ beta = self.target.prior.beta
251
+ gamma = self.target.prior.gamma
252
+
253
+ # Compute likelihood variables
254
+ x = self.target.likelihood.data
255
+ mu = self.target.likelihood.distribution.mean
256
+ L = self.target.likelihood.distribution(np.array([1])).sqrtprec
257
+
258
+ if self.target.likelihood.distribution.preset == "l1":
259
+ m = count_nonzero(x)
260
+ elif self.target.likelihood.distribution.preset == "tv" and isinstance(self.target.likelihood.distribution.geometry, Continuous1D):
261
+ m = count_constant_components_1D(x)
262
+ elif self.target.likelihood.distribution.preset == "tv" and isinstance(self.target.likelihood.distribution.geometry, (Continuous2D, Image2D)):
263
+ m = count_constant_components_2D(self.target.likelihood.distribution.geometry.par2fun(x))
264
+
265
+ reg_op = self.target.likelihood.distribution._regularization_oper
266
+ reg_strength = self.target.likelihood.distribution(np.array([1])).strength
267
+ fx = reg_strength*np.linalg.norm(reg_op@x, ord = 1)
268
+
269
+ # Compute parameters of conjugate distribution
270
+ conj_alpha = m + alpha
271
+ conj_beta = 0.5*np.linalg.norm(L @ (mu - x))**2 + beta
272
+ conj_gamma = -fx + gamma
273
+
274
+ # Create conjugate distribution
275
+ return ModifiedHalfNormal(conj_alpha, conj_beta, conj_gamma)
276
+
171
277
 
172
278
  def _get_conjugate_parameter(target):
173
279
  """Extract the conjugate parameter name (e.g. d), and returns the mutable variable that is defined by the conjugate parameter, e.g. cov and its value e.g. lambda d:1/d"""
@@ -180,12 +286,9 @@ def _get_conjugate_parameter(target):
180
286
  attr = getattr(target.likelihood.distribution, var_key)
181
287
  if callable(attr) and par_name in get_non_default_args(attr):
182
288
  found_parameter_pairs.append((var_key, attr))
183
- if len(found_parameter_pairs) == 1:
184
- return found_parameter_pairs[0]
185
- elif len(found_parameter_pairs) > 1:
186
- raise ValueError(f"Multiple references of parameter {par_name} found in likelihood function for conjugate sampler with target {target}. This is not supported.")
187
- else:
289
+ if len(found_parameter_pairs) == 0:
188
290
  raise ValueError(f"Unable to find conjugate parameter {par_name} in likelihood function for conjugate sampler with target {target}")
291
+ return found_parameter_pairs
189
292
 
190
293
  def _check_conjugate_parameter_is_scalar_identity(f):
191
294
  """Tests whether a function (scalar to scalar) is the identity (lambda x: x)."""
@@ -195,3 +298,38 @@ def _check_conjugate_parameter_is_scalar_identity(f):
195
298
  def _check_conjugate_parameter_is_scalar_reciprocal(f):
196
299
  """Tests whether a function (scalar to scalar) is the reciprocal (lambda x : 1.0/x)."""
197
300
  return all(math.isclose(f(x), 1.0 / x) for x in [1.0, 10.0, 100.0])
301
+
302
+ def _check_conjugate_parameter_is_scalar_linear(f):
303
+ """
304
+ Tests whether a function (scalar to scalar) is linear (lambda x: s*x for some s).
305
+ The tests checks whether the function is zero and some finite differences are constant.
306
+ """
307
+ test_values = [1.0, 10.0, 100.0]
308
+ h = 1e-2
309
+ finite_diffs = [(f(x + h*x)-f(x))/(h*x) for x in test_values]
310
+ return np.isclose(f(0.0), 0.0) and all(np.allclose(c, finite_diffs[0]) for c in finite_diffs[1:])
311
+
312
+ def _check_conjugate_parameter_is_scalar_linear_reciprocal(f):
313
+ """
314
+ Tests whether a function (scalar to scalar) is a constant times the inverse of the input (lambda x: s/x for some s).
315
+ The tests checks whether the the reciprocal of the function has constant finite differences.
316
+ """
317
+ g = lambda x : 1.0/f(x)
318
+ test_values = [1.0, 10.0, 100.0]
319
+ h = 1e-2
320
+ finite_diffs = [(g(x + h*x)-g(x))/(h*x) for x in test_values]
321
+ return all(np.allclose(c, finite_diffs[0]) for c in finite_diffs[1:])
322
+
323
+ def _check_conjugate_parameter_is_scalar_quadratic(f):
324
+ """
325
+ Tests whether a function (scalar to scalar) is linear (lambda x: s*x**2 for some s).
326
+ The tests checks whether the function divided by the parameter is linear
327
+ """
328
+ return _check_conjugate_parameter_is_scalar_linear(lambda x: f(x)/x if x != 0.0 else f(0.0))
329
+
330
+ def _check_conjugate_parameter_is_scalar_quadratic_reciprocal(f):
331
+ """
332
+ Tests whether a function (scalar to scalar) is linear (lambda x: s*x**-2 for some s).
333
+ The tests checks whether the function divided by the parameter is the reciprical of a linear function.
334
+ """
335
+ return _check_conjugate_parameter_is_scalar_linear_reciprocal(lambda x: f(x)/x)
@@ -33,26 +33,23 @@ class _LMRFGammaPair(_ConjugatePair):
33
33
  """ Implementation of the conjugate pair (LMRF, Gamma) """
34
34
 
35
35
  def validate_target(self):
36
- if not isinstance(self.target.likelihood.distribution, LMRF):
37
- raise ValueError("Approximate conjugate sampler only works with LMRF likelihood function")
38
-
39
- if not isinstance(self.target.prior, Gamma):
40
- raise ValueError("Approximate conjugate sampler with LMRF likelihood only works with Gamma prior")
41
-
42
36
  if not self.target.prior.dim == 1:
43
37
  raise ValueError("Approximate conjugate sampler only works with univariate Gamma prior")
44
38
 
45
39
  if np.sum(self.target.likelihood.distribution.location) != 0:
46
40
  raise ValueError("Approximate conjugate sampler only works with zero mean LMRF likelihood")
47
41
 
48
- key, value = _get_conjugate_parameter(self.target)
49
- if key == "scale":
50
- if not _check_conjugate_parameter_is_scalar_reciprocal(value):
51
- raise ValueError("Approximate conjugate sampler only works with Gamma prior on the inverse of the scale parameter of the LMRF likelihood")
52
- else:
53
- raise ValueError(f"No approximate conjugacy defined for likelihood {type(self.target.likelihood.distribution)} and prior {type(self.target.prior)}, in CUQIpy")
42
+ key_value_pairs = _get_conjugate_parameter(self.target)
43
+ if len(key_value_pairs) != 1:
44
+ raise ValueError(f"Multiple references to conjugate parameter {self.target.prior.name} found in likelihood. Only one occurance is supported.")
45
+ for key, value in key_value_pairs:
46
+ if key == "scale":
47
+ if not _check_conjugate_parameter_is_scalar_reciprocal(value):
48
+ raise ValueError("Approximate conjugate sampler only works with Gamma prior on the inverse of the scale parameter of the LMRF likelihood")
49
+ else:
50
+ raise ValueError(f"No approximate conjugacy defined for likelihood {type(self.target.likelihood.distribution)} and prior {type(self.target.prior)}, in CUQIpy")
54
51
 
55
- def sample(self):
52
+ def conjugate_distribution(self):
56
53
  # Extract variables
57
54
  # Here we approximate the LMRF with a Gaussian
58
55
 
@@ -76,6 +73,4 @@ class _LMRFGammaPair(_ConjugatePair):
76
73
  beta = self.target.prior.rate #beta
77
74
 
78
75
  # Create Gamma distribution and sample
79
- dist = Gamma(shape=d+alpha, rate=np.linalg.norm(Lx)**2+beta)
80
-
81
- return dist.sample()
76
+ return Gamma(shape=d+alpha, rate=np.linalg.norm(Lx)**2+beta)
@@ -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):
@@ -5,6 +5,7 @@ from cuqi.geometry import Continuous1D, Continuous2D, Image2D
5
5
  from cuqi.operator import FirstOrderFiniteDifference
6
6
 
7
7
  import numpy as np
8
+ from copy import copy
8
9
 
9
10
 
10
11
  class RegularizedGaussian(Distribution):
@@ -269,29 +270,12 @@ class RegularizedGaussian(Distribution):
269
270
  mutable_vars += ["strength"]
270
271
  return mutable_vars
271
272
 
272
- # Overwrite the condition method such that the underlying Gaussian is conditioned in general, except when conditioning on self.name
273
- # which means we convert Distribution to Likelihood or EvaluatedDensity.
274
- def _condition(self, *args, **kwargs):
275
- if self.preset in self.regularization_options():
276
- return super()._condition(*args, **kwargs)
277
-
278
- # Handle positional arguments (similar code as in Distribution._condition)
279
- cond_vars = self.get_conditioning_variables()
280
- kwargs = self._parse_args_add_to_kwargs(cond_vars, *args, **kwargs)
281
-
282
- # When conditioning, we always do it on a copy to avoid unintentional side effects
283
- new_density = self._make_copy()
284
-
285
- # Check if self.name is in the provided keyword arguments.
286
- # If so, pop it and store its value.
287
- value = kwargs.pop(self.name, None)
288
-
289
- new_density._gaussian = self.gaussian._condition(**kwargs)
290
-
291
- # If self.name was provided, we convert to a likelihood or evaluated density
292
- if value is not None:
293
- new_density = new_density.to_likelihood(value)
294
-
273
+ def _make_copy(self):
274
+ """ Returns a shallow copy of the density keeping a pointer to the original. """
275
+ # Using deepcopy would also copy the underlying geometry, which causes a crash because geometries won't match anymore.
276
+ new_density = copy(self)
277
+ new_density._gaussian = copy(new_density._gaussian)
278
+ new_density._original_density = self
295
279
  return new_density
296
280
 
297
281
 
@@ -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):
cuqi/problem/_problem.py CHANGED
@@ -5,7 +5,7 @@ from typing import Tuple
5
5
 
6
6
  import cuqi
7
7
  from cuqi import config
8
- from cuqi.distribution import Distribution, Gaussian, InverseGamma, LMRF, GMRF, Lognormal, Posterior, Beta, JointDistribution, Gamma, CMRF
8
+ from cuqi.distribution import Distribution, Gaussian, InverseGamma, LMRF, GMRF, Lognormal, Posterior, Beta, JointDistribution, Gamma, ModifiedHalfNormal, CMRF
9
9
  from cuqi.implicitprior import RegularizedGaussian, RegularizedGMRF
10
10
  from cuqi.density import Density
11
11
  from cuqi.model import LinearModel, Model
@@ -923,8 +923,8 @@ class BayesianProblem(object):
923
923
  if not isinstance(cond_target, Posterior):
924
924
  raise NotImplementedError(f"Unable to determine sampling strategy for {par_name} with target {cond_target}")
925
925
 
926
- # Gamma prior, Gaussian likelihood -> Conjugate
927
- if self._check_posterior(cond_target, Gamma, (Gaussian, GMRF, RegularizedGaussian, RegularizedGMRF)):
926
+ # Gamma or ModifiedHalfNormal prior, Gaussian or RegularizedGaussian likelihood -> Conjugate
927
+ if self._check_posterior(cond_target, (Gamma, ModifiedHalfNormal), (Gaussian, GMRF, RegularizedGaussian, RegularizedGMRF)):
928
928
  if experimental:
929
929
  sampling_strategy[par_name] = cuqi.experimental.mcmc.Conjugate()
930
930
  else:
cuqi/solver/_solver.py CHANGED
@@ -884,4 +884,4 @@ def ProximalL1(x, gamma):
884
884
  x : array_like.
885
885
  gamma : scale parameter.
886
886
  """
887
- return np.multiply(np.sign(x), np.maximum(np.abs(x)-gamma, 0))
887
+ return np.multiply(np.sign(x), np.maximum(np.abs(x)-gamma, 0))
@@ -12,7 +12,10 @@ from ._utilities import (
12
12
  approx_derivative,
13
13
  check_if_conditional_from_attr,
14
14
  plot_1D_density,
15
- plot_2D_density
15
+ plot_2D_density,
16
+ count_nonzero,
17
+ count_constant_components_1D,
18
+ count_constant_components_2D
16
19
  )
17
20
 
18
21
  from ._get_python_variable_name import _get_python_variable_name
@@ -344,4 +344,102 @@ def plot_2D_density(density: Density,
344
344
  extent = (v1_min-hp_x, v1_max+hp_x, v2_min-hp_y, v2_max+hp_y)
345
345
 
346
346
  im = plt.imshow(evaluated_density, origin='lower', extent=extent, **kwargs)
347
- return im
347
+ return im
348
+
349
+
350
+
351
+ def count_nonzero(x, threshold = 1e-6):
352
+ """ Returns the number of values in an array whose absolute value is larger than a specified threshold
353
+
354
+ Parameters
355
+ ----------
356
+ x : `np.ndarray`
357
+ Array to count nonzero elements of.
358
+
359
+ threshold : float
360
+ Theshold for considering a value as nonzero.
361
+ """
362
+ return np.sum([np.abs(v) >= threshold for v in x])
363
+
364
+ def count_constant_components_1D(x, threshold = 1e-2, lower = -np.inf, upper = np.inf):
365
+ """ Returns the number of piecewise constant components in a one-dimensional array
366
+
367
+ Parameters
368
+ ----------
369
+ x : `np.ndarray`
370
+ 1D Array to count components of.
371
+
372
+ threshold : float
373
+ Strict theshold on when the difference of neighbouring values is considered zero.
374
+
375
+ lower : float
376
+ Piecewise constant components below this value are not counted.
377
+
378
+ upper : float
379
+ Piecewise constant components above this value are not counted.
380
+ """
381
+
382
+ counter = 0
383
+ if x[0] > lower and x[0] < upper:
384
+ counter += 1
385
+
386
+ x_previous = x[0]
387
+
388
+ for x_current in x[1:]:
389
+ if (abs(x_previous - x_current) >= threshold and
390
+ x_current > lower and
391
+ x_current < upper):
392
+ counter += 1
393
+
394
+ x_previous = x_current
395
+
396
+ return counter
397
+
398
+ def count_constant_components_2D(x, threshold = 1e-2, lower = -np.inf, upper = np.inf):
399
+ """ Returns the number of piecewise constant components in a two-dimensional array
400
+
401
+ Parameters
402
+ ----------
403
+ x : `np.ndarray`
404
+ 2D Array to count components of.
405
+
406
+ threshold : float
407
+ Strict theshold on when the difference of neighbouring values is considered zero.
408
+
409
+ lower : float
410
+ Piecewise constant components below this value are not counted.
411
+
412
+ upper : float
413
+ Piecewise constant components above this value are not counted.
414
+ """
415
+ filled = np.zeros_like(x, dtype = int)
416
+ counter = 0
417
+
418
+ def process(i, j):
419
+ queue = []
420
+ queue.append((i,j))
421
+ filled[i, j] = 1
422
+ while len(queue) != 0:
423
+ (icur, jcur) = queue.pop(0)
424
+
425
+ if icur > 0 and filled[icur - 1, jcur] == 0 and abs(x[icur, jcur] - x[icur - 1, jcur]) <= threshold:
426
+ filled[icur - 1, jcur] = 1
427
+ queue.append((icur-1, jcur))
428
+ if jcur > 0 and filled[icur, jcur-1] == 0 and abs(x[icur, jcur] - x[icur, jcur - 1]) <= threshold:
429
+ filled[icur, jcur-1] = 1
430
+ queue.append((icur, jcur-1))
431
+ if icur < x.shape[0]-1 and filled[icur + 1, jcur] == 0 and abs(x[icur, jcur] - x[icur + 1, jcur]) <= threshold:
432
+ filled[icur + 1, jcur] = 1
433
+ queue.append((icur+1, jcur))
434
+ if jcur < x.shape[1]-1 and filled[icur, jcur + 1] == 0 and abs(x[icur, jcur] - x[icur, jcur + 1]) <= threshold:
435
+ filled[icur, jcur + 1] = 1
436
+ queue.append((icur, jcur+1))
437
+
438
+ for i in range(x.shape[0]):
439
+ for j in range(x.shape[1]):
440
+ if filled[i,j] == 0:
441
+ if x[i,j] > lower and x[i,j] < upper:
442
+ counter += 1
443
+ process(i, j)
444
+ return counter
445
+