CUQIpy 1.2.0.post0.dev501__tar.gz → 1.2.0.post0.dev522__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 (126) hide show
  1. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/CUQIpy.egg-info/PKG-INFO +1 -1
  2. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/PKG-INFO +1 -1
  3. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/_version.py +3 -3
  4. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_conjugate.py +26 -16
  5. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_rto.py +1 -1
  6. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/implicitprior/_regularizedGMRF.py +1 -0
  7. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/implicitprior/_regularizedGaussian.py +128 -47
  8. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/implicitprior/_regularizedUnboundedUniform.py +1 -0
  9. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/_conjugate.py +1 -1
  10. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_implicit_priors.py +27 -6
  11. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/CUQIpy.egg-info/SOURCES.txt +0 -0
  12. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/CUQIpy.egg-info/dependency_links.txt +0 -0
  13. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/CUQIpy.egg-info/requires.txt +0 -0
  14. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/CUQIpy.egg-info/top_level.txt +0 -0
  15. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/LICENSE +0 -0
  16. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/README.md +0 -0
  17. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/__init__.py +0 -0
  18. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/_messages.py +0 -0
  19. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/array/__init__.py +0 -0
  20. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/array/_array.py +0 -0
  21. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/config.py +0 -0
  22. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/data/__init__.py +0 -0
  23. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/data/_data.py +0 -0
  24. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/data/astronaut.npz +0 -0
  25. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/data/camera.npz +0 -0
  26. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/data/cat.npz +0 -0
  27. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/data/cookie.png +0 -0
  28. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/data/satellite.mat +0 -0
  29. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/density/__init__.py +0 -0
  30. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/density/_density.py +0 -0
  31. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/diagnostics.py +0 -0
  32. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/__init__.py +0 -0
  33. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_beta.py +0 -0
  34. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_cauchy.py +0 -0
  35. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_cmrf.py +0 -0
  36. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_custom.py +0 -0
  37. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_distribution.py +0 -0
  38. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_gamma.py +0 -0
  39. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_gaussian.py +0 -0
  40. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_gmrf.py +0 -0
  41. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_inverse_gamma.py +0 -0
  42. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_joint_distribution.py +0 -0
  43. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_laplace.py +0 -0
  44. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_lmrf.py +0 -0
  45. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_lognormal.py +0 -0
  46. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_modifiedhalfnormal.py +0 -0
  47. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_normal.py +0 -0
  48. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_posterior.py +0 -0
  49. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_smoothed_laplace.py +0 -0
  50. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_truncated_normal.py +0 -0
  51. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/distribution/_uniform.py +0 -0
  52. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/__init__.py +0 -0
  53. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/algebra/__init__.py +0 -0
  54. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/algebra/_ast.py +0 -0
  55. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/algebra/_orderedset.py +0 -0
  56. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/algebra/_randomvariable.py +0 -0
  57. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/geometry/__init__.py +0 -0
  58. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/geometry/_productgeometry.py +0 -0
  59. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/__init__.py +0 -0
  60. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_conjugate_approx.py +0 -0
  61. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_cwmh.py +0 -0
  62. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_direct.py +0 -0
  63. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_gibbs.py +0 -0
  64. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_hmc.py +0 -0
  65. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_langevin_algorithm.py +0 -0
  66. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_laplace_approximation.py +0 -0
  67. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_mh.py +0 -0
  68. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_pcn.py +0 -0
  69. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_sampler.py +0 -0
  70. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/experimental/mcmc/_utilities.py +0 -0
  71. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/geometry/__init__.py +0 -0
  72. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/geometry/_geometry.py +0 -0
  73. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/implicitprior/__init__.py +0 -0
  74. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/implicitprior/_restorator.py +0 -0
  75. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/likelihood/__init__.py +0 -0
  76. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/likelihood/_likelihood.py +0 -0
  77. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/model/__init__.py +0 -0
  78. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/model/_model.py +0 -0
  79. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/operator/__init__.py +0 -0
  80. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/operator/_operator.py +0 -0
  81. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/pde/__init__.py +0 -0
  82. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/pde/_pde.py +0 -0
  83. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/problem/__init__.py +0 -0
  84. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/problem/_problem.py +0 -0
  85. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/__init__.py +0 -0
  86. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/_conjugate_approx.py +0 -0
  87. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/_cwmh.py +0 -0
  88. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/_gibbs.py +0 -0
  89. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/_hmc.py +0 -0
  90. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/_langevin_algorithm.py +0 -0
  91. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/_laplace_approximation.py +0 -0
  92. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/_mh.py +0 -0
  93. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/_pcn.py +0 -0
  94. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/_rto.py +0 -0
  95. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/sampler/_sampler.py +0 -0
  96. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/samples/__init__.py +0 -0
  97. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/samples/_samples.py +0 -0
  98. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/solver/__init__.py +0 -0
  99. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/solver/_solver.py +0 -0
  100. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/testproblem/__init__.py +0 -0
  101. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/testproblem/_testproblem.py +0 -0
  102. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/utilities/__init__.py +0 -0
  103. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/utilities/_get_python_variable_name.py +0 -0
  104. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/cuqi/utilities/_utilities.py +0 -0
  105. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/pyproject.toml +0 -0
  106. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/requirements.txt +0 -0
  107. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/setup.cfg +0 -0
  108. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/setup.py +0 -0
  109. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_MRFs.py +0 -0
  110. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_abstract_distribution_density.py +0 -0
  111. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_bayesian_inversion.py +0 -0
  112. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_density.py +0 -0
  113. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_distribution.py +0 -0
  114. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_distributions_shape.py +0 -0
  115. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_geometry.py +0 -0
  116. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_joint_distribution.py +0 -0
  117. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_likelihood.py +0 -0
  118. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_model.py +0 -0
  119. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_pde.py +0 -0
  120. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_posterior.py +0 -0
  121. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_problem.py +0 -0
  122. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_sampler.py +0 -0
  123. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_samples.py +0 -0
  124. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_solver.py +0 -0
  125. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_testproblem.py +0 -0
  126. {cuqipy-1.2.0.post0.dev501 → cuqipy-1.2.0.post0.dev522}/tests/test_utilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: CUQIpy
3
- Version: 1.2.0.post0.dev501
3
+ Version: 1.2.0.post0.dev522
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.dev501
3
+ Version: 1.2.0.post0.dev522
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-30T19:07:50+0100",
11
+ "date": "2025-01-31T10:17:23+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "ead079a625cad175f6a0159d46c90d7cdf80bc0f",
15
- "version": "1.2.0.post0.dev501"
14
+ "full-revisionid": "0303b8f2322f41aeb6304e705b7bbcfde5e2de23",
15
+ "version": "1.2.0.post0.dev522"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -147,7 +147,7 @@ class _RegularizedGaussianGammaPair(_ConjugatePair):
147
147
  if self.target.prior.dim != 1:
148
148
  raise ValueError("RegularizedGaussian-Gamma conjugacy only works with univariate ModifiedHalfNormal prior")
149
149
 
150
- if self.target.likelihood.distribution.preset not in ["nonnegativity"]:
150
+ if self.target.likelihood.distribution.preset["constraint"] not in ["nonnegativity"]:
151
151
  raise ValueError("RegularizedGaussian-Gamma conjugacy only works with implicit regularized Gaussian likelihood with nonnegativity constraints")
152
152
 
153
153
  key_value_pairs = _get_conjugate_parameter(self.target)
@@ -183,7 +183,7 @@ class _RegularizedUnboundedUniformGammaPair(_ConjugatePair):
183
183
  if self.target.prior.dim != 1:
184
184
  raise ValueError("RegularizedUnboundedUniform-Gamma conjugacy only works with univariate Gamma prior")
185
185
 
186
- if self.target.likelihood.distribution.preset not in ["l1", "tv"]:
186
+ if self.target.likelihood.distribution.preset["regularization"] not in ["l1", "tv"]:
187
187
  raise ValueError("RegularizedUnboundedUniform-Gamma conjugacy only works with implicit regularized Gaussian likelihood with l1 or tv regularization")
188
188
 
189
189
  key_value_pairs = _get_conjugate_parameter(self.target)
@@ -203,12 +203,7 @@ class _RegularizedUnboundedUniformGammaPair(_ConjugatePair):
203
203
 
204
204
  # Compute likelihood quantities
205
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))
206
+ m = _compute_sparsity_level(self.target)
212
207
 
213
208
  reg_op = self.target.likelihood.distribution._regularization_oper
214
209
  reg_strength = self.target.likelihood.distribution(np.array([1])).strength
@@ -224,7 +219,7 @@ class _RegularizedGaussianModifiedHalfNormalPair(_ConjugatePair):
224
219
  if self.target.prior.dim != 1:
225
220
  raise ValueError("RegularizedGaussian-ModifiedHalfNormal conjugacy only works with univariate ModifiedHalfNormal prior")
226
221
 
227
- if self.target.likelihood.distribution.preset not in ["l1", "tv"]:
222
+ if self.target.likelihood.distribution.preset["regularization"] not in ["l1", "tv"]:
228
223
  raise ValueError("RegularizedGaussian-ModifiedHalfNormal conjugacy only works with implicit regularized Gaussian likelihood with l1 or tv regularization")
229
224
 
230
225
  key_value_pairs = _get_conjugate_parameter(self.target)
@@ -254,13 +249,8 @@ class _RegularizedGaussianModifiedHalfNormalPair(_ConjugatePair):
254
249
  x = self.target.likelihood.data
255
250
  mu = self.target.likelihood.distribution.mean
256
251
  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))
252
+
253
+ m = _compute_sparsity_level(self.target)
264
254
 
265
255
  reg_op = self.target.likelihood.distribution._regularization_oper
266
256
  reg_strength = self.target.likelihood.distribution(np.array([1])).strength
@@ -275,6 +265,26 @@ class _RegularizedGaussianModifiedHalfNormalPair(_ConjugatePair):
275
265
  return ModifiedHalfNormal(conj_alpha, conj_beta, conj_gamma)
276
266
 
277
267
 
268
+ def _compute_sparsity_level(target):
269
+ """Computes the sparsity level in accordance with Section 4 from [2],"""
270
+ x = target.likelihood.data
271
+ if target.likelihood.distribution.preset["constraint"] == "nonnegativity":
272
+ if target.likelihood.distribution.preset["regularization"] == "l1":
273
+ m = count_nonzero(x)
274
+ elif target.likelihood.distribution.preset["regularization"] == "tv" and isinstance(target.likelihood.distribution.geometry, Continuous1D):
275
+ m = count_constant_components_1D(x, lower = 0.0)
276
+ elif target.likelihood.distribution.preset["regularization"] == "tv" and isinstance(target.likelihood.distribution.geometry, (Continuous2D, Image2D)):
277
+ m = count_constant_components_2D(target.likelihood.distribution.geometry.par2fun(x), lower = 0.0)
278
+ else: # No constraints, only regularization
279
+ if target.likelihood.distribution.preset["regularization"] == "l1":
280
+ m = count_nonzero(x)
281
+ elif target.likelihood.distribution.preset["regularization"] == "tv" and isinstance(target.likelihood.distribution.geometry, Continuous1D):
282
+ m = count_constant_components_1D(x)
283
+ elif target.likelihood.distribution.preset["regularization"] == "tv" and isinstance(target.likelihood.distribution.geometry, (Continuous2D, Image2D)):
284
+ m = count_constant_components_2D(target.likelihood.distribution.geometry.par2fun(x))
285
+ return m
286
+
287
+
278
288
  def _get_conjugate_parameter(target):
279
289
  """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
290
  par_name = target.prior.name
@@ -239,7 +239,7 @@ class RegularizedLinearRTO(LinearRTO):
239
239
  @solver.setter
240
240
  def solver(self, value):
241
241
  if value == "ScipyLinearLSQ":
242
- if (self.target.prior._preset == "nonnegativity" or self.target.prior._preset == "box"):
242
+ if (self.target.prior.preset["constraint"] == "nonnegativity" or self.target.prior.preset["constraint"] == "box"):
243
243
  self._solver = value
244
244
  else:
245
245
  raise ValueError("ScipyLinearLSQ only supports RegularizedGaussian with box or nonnegativity constraint.")
@@ -68,6 +68,7 @@ class RegularizedGMRF(RegularizedGaussian):
68
68
  # Init from abstract distribution class
69
69
  super(Distribution, self).__init__(**kwargs)
70
70
 
71
+ self._force_list = False
71
72
  self._parse_regularization_input_arguments(proximal, projector, constraint, regularization, args)
72
73
 
73
74
 
@@ -2,9 +2,10 @@ from cuqi.utilities import get_non_default_args
2
2
  from cuqi.distribution import Distribution, Gaussian
3
3
  from cuqi.solver import ProjectNonnegative, ProjectBox, ProximalL1
4
4
  from cuqi.geometry import Continuous1D, Continuous2D, Image2D
5
- from cuqi.operator import FirstOrderFiniteDifference
5
+ from cuqi.operator import FirstOrderFiniteDifference, Operator
6
6
 
7
7
  import numpy as np
8
+ import scipy.sparse as sparse
8
9
  from copy import copy
9
10
 
10
11
 
@@ -48,6 +49,8 @@ class RegularizedGaussian(Distribution):
48
49
  min_z 0.5||x-z||_2^2+scale*g(x).
49
50
  If list of tuples (callable proximal operator of f_i, linear operator L_i):
50
51
  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.
52
+ Each linear operator needs to have the '__matmul__', 'T' and 'shape' attributes;
53
+ this includes numpy.ndarray, scipy.sparse.sparray, scipy.sparse.linalg.LinearOperator and cuqi.operator.Operator.
51
54
  The corresponding regularization takes the form
52
55
  sum_i f_i(L_i x),
53
56
  where the sum ranges from 1 to an arbitrary n.
@@ -88,59 +91,137 @@ class RegularizedGaussian(Distribution):
88
91
  # Init from abstract distribution class
89
92
  super().__init__(**kwargs)
90
93
 
94
+ self._force_list = False
91
95
  self._parse_regularization_input_arguments(proximal, projector, constraint, regularization, optional_regularization_parameters)
92
96
 
93
97
  def _parse_regularization_input_arguments(self, proximal, projector, constraint, regularization, optional_regularization_parameters):
94
98
  """ Parse regularization input arguments with guarding statements and store internal states """
95
99
 
96
- # Check that only one of proximal, projector, constraint or regularization is provided
97
- if (proximal is not None) + (projector is not None) + (constraint is not None) + (regularization is not None) != 1:
98
- raise ValueError("Precisely one of proximal, projector, constraint or regularization needs to be provided.")
100
+ # Guards checking whether the regularization inputs are valid
101
+ if (proximal is not None) + (projector is not None) + max((constraint is not None), (regularization is not None)) == 0:
102
+ raise ValueError("At least some constraint or regularization has to be specified for RegularizedGaussian")
103
+
104
+ if (proximal is not None) + (projector is not None) == 2:
105
+ raise ValueError("Only one of proximal or projector can be used.")
106
+
107
+ if (proximal is not None) + (projector is not None) + max((constraint is not None), (regularization is not None)) > 1:
108
+ raise ValueError("User-defined proximals and projectors cannot be combined with pre-defined constraints and regularization.")
109
+
110
+ # Branch between user-defined and preset
111
+ if (proximal is not None) + (projector is not None) >= 1:
112
+ self._parse_user_specified_input(proximal, projector)
113
+ else:
114
+ # Set constraint and regularization presets for use with Gibbs
115
+ self._preset = {"constraint": None,
116
+ "regularization": None}
99
117
 
118
+ self._parse_preset_constraint_input(constraint, optional_regularization_parameters)
119
+ self._parse_preset_regularization_input(regularization, optional_regularization_parameters)
120
+
121
+ # Merge
122
+ self._merge_predefined_option()
123
+
124
+ def _parse_user_specified_input(self, proximal, projector):
125
+ # Guard for checking partial validy of proximals or projectors
126
+ if proximal is not None:
127
+ if callable(proximal):
128
+ if len(get_non_default_args(proximal)) != 2:
129
+ raise ValueError("Proximal should take 2 arguments.")
130
+ elif isinstance(proximal, list):
131
+ for val in proximal:
132
+ if len(val) != 2:
133
+ raise ValueError("Each value in the proximal list needs to consistent of two elements: a proximal operator and a linear operator.")
134
+ if callable(val[0]):
135
+ if len(get_non_default_args(val[0])) != 2:
136
+ raise ValueError("Proximal should take 2 arguments.")
137
+ else:
138
+ raise ValueError("Proximal operators need to be callable.")
139
+ if not (hasattr(val[1], '__matmul__') and hasattr(val[1], 'T') and hasattr(val[1], 'shape')):
140
+ raise ValueError("Linear operator not supported, must have '__matmul__', 'T' and 'shape' attributes.")
141
+ else:
142
+ raise ValueError("Proximal needs to be callable or a list. See documentation.")
143
+
100
144
  if projector is not None:
101
- if not callable(projector):
102
- raise ValueError("Projector needs to be callable.")
103
- if len(get_non_default_args(projector)) != 1:
104
- raise ValueError("Projector should take 1 argument.")
145
+ if callable(projector):
146
+ if len(get_non_default_args(projector)) != 1:
147
+ raise ValueError("Projector should take 1 argument.")
148
+ else:
149
+ raise ValueError("Projector needs to be callable")
105
150
 
106
- # Preset information, for use in Gibbs
107
- self._preset = None
108
-
151
+ # Set user-defined proximals or projectors
109
152
  if proximal is not None:
110
- # No need to generate the proximal and associated information
111
- self.proximal = proximal
112
- elif projector is not None:
153
+ self._preset = None
154
+ self._proximal = proximal
155
+ return
156
+
157
+ if projector is not None:
158
+ self._preset = None
113
159
  self._proximal = lambda z, gamma: projector(z)
114
- elif (isinstance(constraint, str) and constraint.lower() == "nonnegativity"):
115
- self._proximal = lambda z, gamma: ProjectNonnegative(z)
116
- self._preset = "nonnegativity"
117
- self._box_bounds = (np.ones(self.dim)*0, np.ones(self.dim)*np.inf)
118
- elif (isinstance(constraint, str) and constraint.lower() == "box"):
119
- self._box_lower = optional_regularization_parameters["lower_bound"]
120
- self._box_upper = optional_regularization_parameters["upper_bound"]
121
- self._box_bounds = (np.ones(self.dim)*self._box_lower, np.ones(self.dim)*self._box_upper)
122
- self._proximal = lambda z, _: ProjectBox(z, self._box_lower, self._box_upper)
123
- self._preset = "box" # Not supported in Gibbs
124
- elif (isinstance(regularization, str) and regularization.lower() in ["l1"]):
125
- self._strength = optional_regularization_parameters["strength"]
126
- self._proximal = lambda z, gamma: ProximalL1(z, gamma*self._strength)
127
- self._preset = "l1"
128
- elif (isinstance(regularization, str) and regularization.lower() in ["tv"]):
160
+ return
161
+
162
+ def _parse_preset_constraint_input(self, constraint, optional_regularization_parameters):
163
+ # Create data for constraints
164
+ self._constraint_prox = None
165
+ self._constraint_oper = None
166
+ if constraint is not None:
167
+ if not isinstance(constraint, str):
168
+ raise ValueError("Constraint needs to be specified as a string.")
169
+
170
+ c_lower = constraint.lower()
171
+ if c_lower == "nonnegativity":
172
+ self._constraint_prox = lambda z, gamma: ProjectNonnegative(z)
173
+ self._box_bounds = (np.ones(self.dim)*0, np.ones(self.dim)*np.inf)
174
+ self._preset["constraint"] = "nonnegativity"
175
+ elif c_lower == "box":
176
+ _box_lower = optional_regularization_parameters["lower_bound"]
177
+ _box_upper = optional_regularization_parameters["upper_bound"]
178
+ self._proximal = lambda z, _: ProjectBox(z, _box_lower, _box_upper)
179
+ self._box_bounds = (np.ones(self.dim)*_box_lower, np.ones(self.dim)*_box_upper)
180
+ self._preset["constraint"] = "box"
181
+ else:
182
+ raise ValueError("Constraint not supported.")
183
+
184
+ def _parse_preset_regularization_input(self, regularization, optional_regularization_parameters):
185
+ # Create data for regularization
186
+ self._regularization_prox = None
187
+ self._regularization_oper = None
188
+ if regularization is not None:
189
+ if not isinstance(regularization, str):
190
+ raise ValueError("Regularization needs to be specified as a string.")
191
+
129
192
  self._strength = optional_regularization_parameters["strength"]
130
- if isinstance(self.geometry, (Continuous1D, Continuous2D, Image2D)):
131
- self._transformation = FirstOrderFiniteDifference(self.geometry.fun_shape, bc_type='neumann')
193
+ r_lower = regularization.lower()
194
+ if r_lower == "l1":
195
+ self._regularization_prox = lambda z, gamma: ProximalL1(z, gamma*self._strength)
196
+ self._preset["regularization"] = "l1"
197
+ elif r_lower == "tv":
198
+ # Store the transformation to reuse when modifying the strength
199
+ if not isinstance(self.geometry, (Continuous1D, Continuous2D, Image2D)):
200
+ raise ValueError("Geometry not supported for total variation")
201
+ self._regularization_prox = lambda z, gamma: ProximalL1(z, gamma*self._strength)
202
+ self._regularization_oper = FirstOrderFiniteDifference(self.geometry.fun_shape, bc_type='neumann')
203
+ self._preset["regularization"] = "tv"
132
204
  else:
133
- raise ValueError("Geometry not supported for total variation")
134
-
135
- self._regularization_prox = lambda z, gamma: ProximalL1(z, gamma*self._strength)
136
- self._regularization_oper = self._transformation
137
-
138
- self._proximal = [(self._regularization_prox, self._regularization_oper)]
139
- self._preset = "tv"
140
- else:
141
- raise ValueError("Regularization not supported")
142
-
205
+ raise ValueError("Regularization not supported.")
143
206
 
207
+ def _merge_predefined_option(self):
208
+ # Check whether it is a single proximal and hence FISTA could be used in RegularizedLinearRTO
209
+ if ((not self._force_list) and
210
+ ((self._constraint_prox is not None) + (self._regularization_prox is not None) == 1) and
211
+ ((self._constraint_oper is not None) + (self._regularization_oper is not None) == 0)):
212
+ if self._constraint_prox is not None:
213
+ self._proximal = self._constraint_prox
214
+ else:
215
+ self._proximal = self._regularization_prox
216
+ return
217
+
218
+ # Merge regularization choices in list for use in ADMM by RegularizedLinearRTO
219
+ self._proximal = []
220
+ if self._constraint_prox is not None:
221
+ self._proximal += [(self._constraint_prox, self._constraint_oper if self._constraint_oper is not None else sparse.eye(self.geometry.par_dim))]
222
+ if self._regularization_prox is not None:
223
+ self._proximal += [(self._regularization_prox, self._regularization_oper if self._regularization_oper is not None else sparse.eye(self.geometry.par_dim))]
224
+
144
225
  @property
145
226
  def transformation(self):
146
227
  return self._transformation
@@ -151,15 +232,15 @@ class RegularizedGaussian(Distribution):
151
232
 
152
233
  @strength.setter
153
234
  def strength(self, value):
154
- if self._preset not in self.regularization_options():
235
+ if self._preset is None or self._preset["regularization"] is None:
155
236
  raise TypeError("Strength is only used when the regularization is set to l1 or TV.")
156
237
 
157
238
  self._strength = value
158
- if self._preset == "tv":
239
+ if self._preset["regularization"] in ["l1", "tv"]:
159
240
  self._regularization_prox = lambda z, gamma: ProximalL1(z, gamma*self._strength)
160
- self._proximal = [(self._regularization_prox, self._regularization_oper)]
161
- elif self._preset == "l1":
162
- self._proximal = lambda z, gamma: ProximalL1(z, gamma*self._strength)
241
+
242
+ # Create new list of proximals based on updated regularization
243
+ self._merge_predefined_option()
163
244
 
164
245
  # This is a getter only attribute for the underlying Gaussian
165
246
  # It also ensures that the name of the underlying Gaussian
@@ -266,7 +347,7 @@ class RegularizedGaussian(Distribution):
266
347
 
267
348
  def get_mutable_variables(self):
268
349
  mutable_vars = self.gaussian.get_mutable_variables().copy()
269
- if self.preset in self.regularization_options():
350
+ if self.preset is not None and self.preset['regularization'] in ["l1", "tv"]:
270
351
  mutable_vars += ["strength"]
271
352
  return mutable_vars
272
353
 
@@ -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)
@@ -28,7 +28,7 @@ class Conjugate: # TODO: Subclass from Sampler once updated
28
28
  if not target.prior.dim == 1:
29
29
  raise ValueError("Conjugate sampler only works with univariate Gamma prior")
30
30
 
31
- if isinstance(target.likelihood.distribution, (RegularizedGaussian, RegularizedGMRF)) and target.likelihood.distribution.preset not in ["nonnegativity"]:
31
+ if isinstance(target.likelihood.distribution, (RegularizedGaussian, RegularizedGMRF)) and (target.likelihood.distribution.preset["constraint"] not in ["nonnegativity"] or target.likelihood.distribution.preset["regularization"] is not None) :
32
32
  raise ValueError("Conjugate sampler only works implicit regularized Gaussian likelihood with nonnegativity constraints")
33
33
 
34
34
  self.target = target
@@ -5,14 +5,14 @@ import pytest
5
5
  def test_RegularizedGaussian_default_init():
6
6
  """ Test that the implicit regularized Gaussian requires at least 1 regularization argument """
7
7
 
8
- with pytest.raises(ValueError, match="Precisely one of "):
8
+ with pytest.raises(ValueError, match="At least some "):
9
9
  x = cuqi.implicitprior.RegularizedGaussian(np.zeros(5), 1)
10
10
 
11
11
  def test_RegularizedGaussian_guarding_statements():
12
12
  """ Test that we catch incorrect initialization of RegularizedGaussian """
13
13
 
14
14
  # More than 1 argument
15
- with pytest.raises(ValueError, match="Precisely one of "):
15
+ with pytest.raises(ValueError, match="User-defined proximals and "):
16
16
  cuqi.implicitprior.RegularizedGaussian(np.zeros(5), 1, proximal=lambda s,z: s, constraint="nonnegativity")
17
17
 
18
18
  # Proximal
@@ -97,7 +97,8 @@ def test_ConstrainedGaussian_alias():
97
97
  x = cuqi.implicitprior.ConstrainedGaussian(np.zeros(5), 1, constraint="nonnegativity")
98
98
 
99
99
  assert isinstance(x, cuqi.implicitprior.RegularizedGaussian)
100
- assert x.preset == "nonnegativity"
100
+ assert x.preset["constraint"] == "nonnegativity"
101
+ assert x.preset["regularization"] is None
101
102
 
102
103
  def test_NonnegativeGaussian_alias():
103
104
  """ Test that the implicit nonnegative Gaussian is a correct allias for an implicit regularized Gaussian """
@@ -105,7 +106,8 @@ def test_NonnegativeGaussian_alias():
105
106
  x = cuqi.implicitprior.NonnegativeGaussian(np.zeros(5), 1)
106
107
 
107
108
  assert isinstance(x, cuqi.implicitprior.RegularizedGaussian)
108
- assert x.preset == "nonnegativity"
109
+ assert x.preset["constraint"] == "nonnegativity"
110
+ assert x.preset["regularization"] is None
109
111
 
110
112
  def test_ConstrainedGMRF_alias():
111
113
  """ Test that the implicit constrained GMRF is a correct allias for an implicit regularized GMRF """
@@ -113,7 +115,8 @@ def test_ConstrainedGMRF_alias():
113
115
  x = cuqi.implicitprior.ConstrainedGMRF(np.zeros(5), 1, constraint="nonnegativity")
114
116
 
115
117
  assert isinstance(x, cuqi.implicitprior.RegularizedGMRF)
116
- assert x.preset == "nonnegativity"
118
+ assert x.preset["constraint"] == "nonnegativity"
119
+ assert x.preset["regularization"] is None
117
120
 
118
121
  def test_NonnegativeGMRF_alias():
119
122
  """ Test that the implicit nonnegative GMRF is a correct allias for an implicit regularized GMRF """
@@ -121,7 +124,8 @@ def test_NonnegativeGMRF_alias():
121
124
  x = cuqi.implicitprior.NonnegativeGMRF(np.zeros(5), 1)
122
125
 
123
126
  assert isinstance(x, cuqi.implicitprior.RegularizedGMRF)
124
- assert x.preset == "nonnegativity"
127
+ assert x.preset["constraint"] == "nonnegativity"
128
+ assert x.preset["regularization"] is None
125
129
 
126
130
  def test_RegularizedUnboundedUniform_is_RegularizedGaussian():
127
131
  """ Test that the implicit regularized unbounded uniform create a Regularized Gaussian with zero sqrtprec """
@@ -164,3 +168,20 @@ def test_RegularizedGaussian_conditioning_strength():
164
168
  assert np.allclose(x.mean, [1, 1, 1, 1])
165
169
  assert np.allclose(x.prec, 10)
166
170
  assert np.allclose(x.strength, 6)
171
+
172
+ def test_RegularizedGaussian_double_preset():
173
+ """ Test that the implicit RegularizedGaussian can handle combined regularization and constraint presets """
174
+
175
+ constraint = "nonnegativity"
176
+ regularization = "tv"
177
+ x = cuqi.implicitprior.RegularizedGaussian(np.zeros(5), 1,
178
+ regularization = regularization, strength = 5,
179
+ constraint = constraint)
180
+
181
+ # Check that the correct presets are set
182
+ assert x.preset["constraint"] == constraint
183
+ assert x.preset["regularization"] == regularization
184
+ # Check whether the constructed proximal list is of correct size
185
+ assert len(x.proximal) == 2
186
+ assert len(x.proximal[0]) == 2
187
+ assert len(x.proximal[1]) == 2