CUQIpy 1.2.0.post0.dev446__tar.gz → 1.2.0.post0.dev481__tar.gz

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

Potentially problematic release.


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

Files changed (127) hide show
  1. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/CUQIpy.egg-info/PKG-INFO +1 -1
  2. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/PKG-INFO +1 -1
  3. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/_version.py +3 -3
  4. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_modifiedhalfnormal.py +13 -16
  5. cuqipy-1.2.0.post0.dev481/cuqi/experimental/mcmc/_conjugate.py +335 -0
  6. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_conjugate_approx.py +11 -16
  7. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/implicitprior/_regularizedGMRF.py +2 -2
  8. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/implicitprior/_regularizedGaussian.py +7 -23
  9. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/implicitprior/_regularizedUnboundedUniform.py +2 -2
  10. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/problem/_problem.py +3 -3
  11. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/utilities/__init__.py +4 -1
  12. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/utilities/_utilities.py +99 -1
  13. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_distribution.py +4 -5
  14. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_distributions_shape.py +1 -0
  15. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_utilities.py +34 -2
  16. cuqipy-1.2.0.post0.dev446/cuqi/experimental/mcmc/_conjugate.py +0 -197
  17. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/CUQIpy.egg-info/SOURCES.txt +0 -0
  18. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/CUQIpy.egg-info/dependency_links.txt +0 -0
  19. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/CUQIpy.egg-info/requires.txt +0 -0
  20. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/CUQIpy.egg-info/top_level.txt +0 -0
  21. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/LICENSE +0 -0
  22. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/README.md +0 -0
  23. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/__init__.py +0 -0
  24. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/_messages.py +0 -0
  25. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/array/__init__.py +0 -0
  26. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/array/_array.py +0 -0
  27. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/config.py +0 -0
  28. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/data/__init__.py +0 -0
  29. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/data/_data.py +0 -0
  30. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/data/astronaut.npz +0 -0
  31. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/data/camera.npz +0 -0
  32. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/data/cat.npz +0 -0
  33. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/data/cookie.png +0 -0
  34. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/data/satellite.mat +0 -0
  35. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/density/__init__.py +0 -0
  36. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/density/_density.py +0 -0
  37. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/diagnostics.py +0 -0
  38. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/__init__.py +0 -0
  39. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_beta.py +0 -0
  40. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_cauchy.py +0 -0
  41. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_cmrf.py +0 -0
  42. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_custom.py +0 -0
  43. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_distribution.py +0 -0
  44. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_gamma.py +0 -0
  45. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_gaussian.py +0 -0
  46. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_gmrf.py +0 -0
  47. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_inverse_gamma.py +0 -0
  48. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_joint_distribution.py +0 -0
  49. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_laplace.py +0 -0
  50. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_lmrf.py +0 -0
  51. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_lognormal.py +0 -0
  52. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_normal.py +0 -0
  53. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_posterior.py +0 -0
  54. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_smoothed_laplace.py +0 -0
  55. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_truncated_normal.py +0 -0
  56. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/distribution/_uniform.py +0 -0
  57. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/__init__.py +0 -0
  58. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/algebra/__init__.py +0 -0
  59. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/algebra/_ast.py +0 -0
  60. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/algebra/_orderedset.py +0 -0
  61. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/algebra/_randomvariable.py +0 -0
  62. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/geometry/__init__.py +0 -0
  63. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/geometry/_productgeometry.py +0 -0
  64. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/__init__.py +0 -0
  65. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_cwmh.py +0 -0
  66. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_direct.py +0 -0
  67. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_gibbs.py +0 -0
  68. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_hmc.py +0 -0
  69. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_langevin_algorithm.py +0 -0
  70. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_laplace_approximation.py +0 -0
  71. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_mh.py +0 -0
  72. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_pcn.py +0 -0
  73. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_rto.py +0 -0
  74. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_sampler.py +0 -0
  75. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/experimental/mcmc/_utilities.py +0 -0
  76. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/geometry/__init__.py +0 -0
  77. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/geometry/_geometry.py +0 -0
  78. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/implicitprior/__init__.py +0 -0
  79. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/implicitprior/_restorator.py +0 -0
  80. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/likelihood/__init__.py +0 -0
  81. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/likelihood/_likelihood.py +0 -0
  82. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/model/__init__.py +0 -0
  83. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/model/_model.py +0 -0
  84. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/operator/__init__.py +0 -0
  85. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/operator/_operator.py +0 -0
  86. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/pde/__init__.py +0 -0
  87. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/pde/_pde.py +0 -0
  88. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/problem/__init__.py +0 -0
  89. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/__init__.py +0 -0
  90. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/_conjugate.py +0 -0
  91. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/_conjugate_approx.py +0 -0
  92. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/_cwmh.py +0 -0
  93. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/_gibbs.py +0 -0
  94. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/_hmc.py +0 -0
  95. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/_langevin_algorithm.py +0 -0
  96. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/_laplace_approximation.py +0 -0
  97. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/_mh.py +0 -0
  98. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/_pcn.py +0 -0
  99. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/_rto.py +0 -0
  100. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/sampler/_sampler.py +0 -0
  101. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/samples/__init__.py +0 -0
  102. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/samples/_samples.py +0 -0
  103. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/solver/__init__.py +0 -0
  104. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/solver/_solver.py +0 -0
  105. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/testproblem/__init__.py +0 -0
  106. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/testproblem/_testproblem.py +0 -0
  107. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/cuqi/utilities/_get_python_variable_name.py +0 -0
  108. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/pyproject.toml +0 -0
  109. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/requirements.txt +0 -0
  110. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/setup.cfg +0 -0
  111. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/setup.py +0 -0
  112. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_MRFs.py +0 -0
  113. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_abstract_distribution_density.py +0 -0
  114. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_bayesian_inversion.py +0 -0
  115. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_density.py +0 -0
  116. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_geometry.py +0 -0
  117. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_implicit_priors.py +0 -0
  118. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_joint_distribution.py +0 -0
  119. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_likelihood.py +0 -0
  120. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_model.py +0 -0
  121. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_pde.py +0 -0
  122. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_posterior.py +0 -0
  123. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_problem.py +0 -0
  124. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_sampler.py +0 -0
  125. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_samples.py +0 -0
  126. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_solver.py +0 -0
  127. {cuqipy-1.2.0.post0.dev446 → cuqipy-1.2.0.post0.dev481}/tests/test_testproblem.py +0 -0
@@ -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.dev481
4
4
  Summary: Computational Uncertainty Quantification for Inverse problems in Python
5
5
  Maintainer-email: "Nicolai A. B. Riis" <nabr@dtu.dk>, "Jakob S. Jørgensen" <jakj@dtu.dk>, "Amal M. Alghamdi" <amaal@dtu.dk>, Chao Zhang <chaz@dtu.dk>
6
6
  License: Apache License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: CUQIpy
3
- Version: 1.2.0.post0.dev446
3
+ Version: 1.2.0.post0.dev481
4
4
  Summary: Computational Uncertainty Quantification for Inverse problems in Python
5
5
  Maintainer-email: "Nicolai A. B. Riis" <nabr@dtu.dk>, "Jakob S. Jørgensen" <jakj@dtu.dk>, "Amal M. Alghamdi" <amaal@dtu.dk>, Chao Zhang <chaz@dtu.dk>
6
6
  License: Apache License
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-01-20T14:37:50+0100",
11
+ "date": "2025-01-20T15:07:59+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "04ade2ac5f81fefc462cb802ec723be5f10e0f0a",
15
- "version": "1.2.0.post0.dev446"
14
+ "full-revisionid": "4ee7ceaf67cf4aafa65188e4aa7194ea6df411e3",
15
+ "version": "1.2.0.post0.dev481"
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
 
@@ -0,0 +1,335 @@
1
+ import numpy as np
2
+ from abc import ABC, abstractmethod
3
+ import math
4
+ from cuqi.experimental.mcmc import Sampler
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
9
+
10
+ class Conjugate(Sampler):
11
+ """ Conjugate sampler
12
+
13
+ Sampler for sampling a posterior distribution which is a so-called "conjugate" distribution, i.e., where the likelihood and prior are conjugate to each other - denoted as a conjugate pair.
14
+
15
+ Currently supported conjugate pairs are:
16
+ - (Gaussian, Gamma) where Gamma is defined on the precision parameter of the Gaussian
17
+ - (GMRF, Gamma) where Gamma is defined on the precision parameter of the GMRF
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
22
+
23
+ Currently the Gamma and ModifiedHalfNormal distribution must be univariate.
24
+
25
+ A conjugate pair defines implicitly a so-called conjugate distribution which can be sampled from directly.
26
+
27
+ The conjugate parameter is the parameter that both the likelihood and prior PDF depend on.
28
+
29
+ For more information on conjugacy and conjugate distributions see https://en.wikipedia.org/wiki/Conjugate_prior.
30
+
31
+ For implicit regularized Gaussians and the corresponding conjugacy relations, see:
32
+
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.
35
+
36
+ """
37
+
38
+ def _initialize(self):
39
+ pass
40
+
41
+ @Sampler.target.setter # Overwrite the target setter to set the conjugate pair
42
+ def target(self, value):
43
+ """ Set the target density. Runs validation of the target. """
44
+ self._target = value
45
+ if self._target is not None:
46
+ self._set_conjugatepair()
47
+ self.validate_target()
48
+
49
+ def validate_target(self):
50
+ self._ensure_target_is_posterior()
51
+ self._conjugatepair.validate_target()
52
+
53
+ def step(self):
54
+ self.current_point = self._conjugatepair.sample()
55
+ return 1 # Returns acceptance rate of 1
56
+
57
+ def tune(self, skip_len, update_count):
58
+ pass # No tuning required for conjugate sampler
59
+
60
+ def _ensure_target_is_posterior(self):
61
+ """ Ensure that the target is a Posterior distribution. """
62
+ if not isinstance(self.target, Posterior):
63
+ raise TypeError("Conjugate sampler requires a target of type Posterior")
64
+
65
+ def _set_conjugatepair(self):
66
+ """ Set the conjugate pair based on the likelihood and prior. This requires target to be set. """
67
+ self._ensure_target_is_posterior()
68
+ if isinstance(self.target.likelihood.distribution, (Gaussian, GMRF)) and isinstance(self.target.prior, Gamma):
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)
73
+ elif isinstance(self.target.likelihood.distribution, (RegularizedGaussian, RegularizedGMRF)) and isinstance(self.target.prior, Gamma):
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)
77
+ else:
78
+ raise ValueError(f"Conjugacy is not defined for likelihood {type(self.target.likelihood.distribution)} and prior {type(self.target.prior)}, in CUQIpy")
79
+
80
+ def conjugate_distribution(self):
81
+ return self._conjugatepair.conjugate_distribution()
82
+
83
+ def __repr__(self):
84
+ msg = super().__repr__()
85
+ if hasattr(self, "_conjugatepair"):
86
+ msg += f"\n Conjugate pair:\n\t {type(self._conjugatepair).__name__.removeprefix('_')}"
87
+ return msg
88
+
89
+ class _ConjugatePair(ABC):
90
+ """ Abstract base class for conjugate pairs (likelihood, prior) used in the Conjugate sampler. """
91
+
92
+ def __init__(self, target):
93
+ self.target = target
94
+
95
+ @abstractmethod
96
+ def validate_target(self):
97
+ """ Validate the target distribution for the conjugate pair. """
98
+ pass
99
+
100
+ @abstractmethod
101
+ def conjugate_distribution(self):
102
+ """ Returns the posterior distribution in the form of a CUQIpy distribution """
103
+ pass
104
+
105
+ def sample(self):
106
+ """ Sample from the conjugate distribution. """
107
+ return self.conjugate_distribution().sample()
108
+
109
+
110
+ class _GaussianGammaPair(_ConjugatePair):
111
+ """ Implementation for the Gaussian-Gamma conjugate pair."""
112
+
113
+ def validate_target(self):
114
+ if self.target.prior.dim != 1:
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):
131
+ # Extract variables
132
+ b = self.target.likelihood.data # mu
133
+ m = len(b) # n
134
+ Ax = self.target.likelihood.distribution.mean # x_i
135
+ L = self.target.likelihood.distribution(np.array([1])).sqrtprec # L
136
+ alpha = self.target.prior.shape # alpha
137
+ beta = self.target.prior.rate # beta
138
+
139
+ # Create Gamma distribution and sample
140
+ return Gamma(shape=m/2 + alpha, rate=.5 * np.linalg.norm(L @ (Ax - b))**2 + beta)
141
+
142
+
143
+ class _RegularizedGaussianGammaPair(_ConjugatePair):
144
+ """Implementation for the Regularized Gaussian-Gamma conjugate pair using the conjugacy rules from [1], Section 3.3."""
145
+
146
+ def validate_target(self):
147
+ if self.target.prior.dim != 1:
148
+ raise ValueError("RegularizedGaussian-Gamma conjugacy only works with univariate ModifiedHalfNormal prior")
149
+
150
+ if self.target.likelihood.distribution.preset not in ["nonnegativity"]:
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):
167
+ # Extract variables
168
+ b = self.target.likelihood.data # mu
169
+ m = np.count_nonzero(b) # n
170
+ Ax = self.target.likelihood.distribution.mean # x_i
171
+ L = self.target.likelihood.distribution(np.array([1])).sqrtprec # L
172
+ alpha = self.target.prior.shape # alpha
173
+ beta = self.target.prior.rate # beta
174
+
175
+ # Create Gamma distribution and sample
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."""
181
+
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
+
277
+
278
+ def _get_conjugate_parameter(target):
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"""
280
+ par_name = target.prior.name
281
+ mutable_likelihood_vars = target.likelihood.distribution.get_mutable_variables()
282
+
283
+ found_parameter_pairs = []
284
+
285
+ for var_key in mutable_likelihood_vars:
286
+ attr = getattr(target.likelihood.distribution, var_key)
287
+ if callable(attr) and par_name in get_non_default_args(attr):
288
+ found_parameter_pairs.append((var_key, attr))
289
+ if len(found_parameter_pairs) == 0:
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
292
+
293
+ def _check_conjugate_parameter_is_scalar_identity(f):
294
+ """Tests whether a function (scalar to scalar) is the identity (lambda x: x)."""
295
+ test_values = [1.0, 10.0, 100.0]
296
+ return all(np.allclose(f(x), x) for x in test_values)
297
+
298
+ def _check_conjugate_parameter_is_scalar_reciprocal(f):
299
+ """Tests whether a function (scalar to scalar) is the reciprocal (lambda x : 1.0/x)."""
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):
@@ -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:
@@ -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