CUQIpy 1.0.0.post0.dev384__tar.gz → 1.0.0.post0.dev420__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 (119) hide show
  1. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/CUQIpy.egg-info/PKG-INFO +1 -1
  2. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/PKG-INFO +1 -1
  3. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/_version.py +3 -3
  4. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_custom.py +2 -2
  5. cuqipy-1.0.0.post0.dev420/cuqi/experimental/mcmc/_conjugate.py +197 -0
  6. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_conjugate_approx.py +24 -18
  7. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_direct.py +1 -0
  8. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/problem/_problem.py +1 -2
  9. cuqipy-1.0.0.post0.dev384/cuqi/experimental/mcmc/_conjugate.py +0 -77
  10. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/CUQIpy.egg-info/SOURCES.txt +0 -0
  11. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/CUQIpy.egg-info/dependency_links.txt +0 -0
  12. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/CUQIpy.egg-info/requires.txt +0 -0
  13. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/CUQIpy.egg-info/top_level.txt +0 -0
  14. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/LICENSE +0 -0
  15. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/README.md +0 -0
  16. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/__init__.py +0 -0
  17. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/_messages.py +0 -0
  18. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/array/__init__.py +0 -0
  19. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/array/_array.py +0 -0
  20. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/config.py +0 -0
  21. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/data/__init__.py +0 -0
  22. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/data/_data.py +0 -0
  23. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/data/astronaut.npz +0 -0
  24. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/data/camera.npz +0 -0
  25. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/data/cat.npz +0 -0
  26. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/data/cookie.png +0 -0
  27. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/data/satellite.mat +0 -0
  28. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/density/__init__.py +0 -0
  29. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/density/_density.py +0 -0
  30. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/diagnostics.py +0 -0
  31. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/__init__.py +0 -0
  32. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_beta.py +0 -0
  33. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_cauchy.py +0 -0
  34. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_cmrf.py +0 -0
  35. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_distribution.py +0 -0
  36. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_gamma.py +0 -0
  37. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_gaussian.py +0 -0
  38. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_gmrf.py +0 -0
  39. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_inverse_gamma.py +0 -0
  40. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_joint_distribution.py +0 -0
  41. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_laplace.py +0 -0
  42. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_lmrf.py +0 -0
  43. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_lognormal.py +0 -0
  44. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_modifiedhalfnormal.py +0 -0
  45. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_normal.py +0 -0
  46. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_posterior.py +0 -0
  47. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_smoothed_laplace.py +0 -0
  48. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/distribution/_uniform.py +0 -0
  49. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/__init__.py +0 -0
  50. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/__init__.py +0 -0
  51. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_cwmh.py +0 -0
  52. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_gibbs.py +0 -0
  53. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_hmc.py +0 -0
  54. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_langevin_algorithm.py +0 -0
  55. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_laplace_approximation.py +0 -0
  56. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_mh.py +0 -0
  57. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_pcn.py +0 -0
  58. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_rto.py +0 -0
  59. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_sampler.py +0 -0
  60. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/experimental/mcmc/_utilities.py +0 -0
  61. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/geometry/__init__.py +0 -0
  62. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/geometry/_geometry.py +0 -0
  63. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/implicitprior/__init__.py +0 -0
  64. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/implicitprior/_regularizedGMRF.py +0 -0
  65. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/implicitprior/_regularizedGaussian.py +0 -0
  66. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/implicitprior/_regularizedUnboundedUniform.py +0 -0
  67. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/likelihood/__init__.py +0 -0
  68. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/likelihood/_likelihood.py +0 -0
  69. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/model/__init__.py +0 -0
  70. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/model/_model.py +0 -0
  71. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/operator/__init__.py +0 -0
  72. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/operator/_operator.py +0 -0
  73. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/pde/__init__.py +0 -0
  74. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/pde/_pde.py +0 -0
  75. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/problem/__init__.py +0 -0
  76. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/__init__.py +0 -0
  77. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/_conjugate.py +0 -0
  78. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/_conjugate_approx.py +0 -0
  79. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/_cwmh.py +0 -0
  80. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/_gibbs.py +0 -0
  81. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/_hmc.py +0 -0
  82. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/_langevin_algorithm.py +0 -0
  83. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/_laplace_approximation.py +0 -0
  84. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/_mh.py +0 -0
  85. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/_pcn.py +0 -0
  86. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/_rto.py +0 -0
  87. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/sampler/_sampler.py +0 -0
  88. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/samples/__init__.py +0 -0
  89. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/samples/_samples.py +0 -0
  90. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/solver/__init__.py +0 -0
  91. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/solver/_solver.py +0 -0
  92. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/testproblem/__init__.py +0 -0
  93. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/testproblem/_testproblem.py +0 -0
  94. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/utilities/__init__.py +0 -0
  95. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/utilities/_get_python_variable_name.py +0 -0
  96. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/cuqi/utilities/_utilities.py +0 -0
  97. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/pyproject.toml +0 -0
  98. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/requirements.txt +0 -0
  99. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/setup.cfg +0 -0
  100. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/setup.py +0 -0
  101. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_MRFs.py +0 -0
  102. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_abstract_distribution_density.py +0 -0
  103. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_bayesian_inversion.py +0 -0
  104. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_density.py +0 -0
  105. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_distribution.py +0 -0
  106. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_distributions_shape.py +0 -0
  107. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_geometry.py +0 -0
  108. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_implicit_priors.py +0 -0
  109. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_joint_distribution.py +0 -0
  110. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_likelihood.py +0 -0
  111. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_model.py +0 -0
  112. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_pde.py +0 -0
  113. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_posterior.py +0 -0
  114. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_problem.py +0 -0
  115. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_sampler.py +0 -0
  116. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_samples.py +0 -0
  117. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_solver.py +0 -0
  118. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_testproblem.py +0 -0
  119. {cuqipy-1.0.0.post0.dev384 → cuqipy-1.0.0.post0.dev420}/tests/test_utilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: CUQIpy
3
- Version: 1.0.0.post0.dev384
3
+ Version: 1.0.0.post0.dev420
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.1
2
2
  Name: CUQIpy
3
- Version: 1.0.0.post0.dev384
3
+ Version: 1.0.0.post0.dev420
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": "2024-07-05T09:29:07+0200",
11
+ "date": "2024-07-05T09:58:51+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "3e3d762fab019f39257708bb9cdf5abc3681f196",
15
- "version": "1.0.0.post0.dev384"
14
+ "full-revisionid": "c77d3deee70affd3a987b7a639bd576c4fe3896a",
15
+ "version": "1.0.0.post0.dev420"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -50,13 +50,13 @@ class UserDefinedDistribution(Distribution):
50
50
  if self.logpdf_func is not None:
51
51
  return self.logpdf_func(x)
52
52
  else:
53
- raise Exception("logpdf_func is not defined.")
53
+ raise NotImplementedError("logpdf_func is not defined.")
54
54
 
55
55
  def _gradient(self, x):
56
56
  if self.gradient_func is not None:
57
57
  return self.gradient_func(x)
58
58
  else:
59
- raise Exception("gradient_func is not defined.")
59
+ raise NotImplementedError("gradient_func is not defined.")
60
60
 
61
61
  def _sample(self, N=1, rng=None):
62
62
  #TODO(nabr) allow sampling more than 1 sample and potentially rng?
@@ -0,0 +1,197 @@
1
+ import numpy as np
2
+ from abc import ABC, abstractmethod
3
+ import math
4
+ from cuqi.experimental.mcmc import SamplerNew
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
8
+
9
+ class ConjugateNew(SamplerNew):
10
+ """ Conjugate sampler
11
+
12
+ 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.
13
+
14
+ Currently supported conjugate pairs are:
15
+ - (Gaussian, Gamma) where Gamma is defined on the precision parameter of the Gaussian
16
+ - (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
19
+
20
+ Currently the Gamma distribution must be univariate.
21
+
22
+ A conjugate pair defines implicitly a so-called conjugate distribution which can be sampled from directly.
23
+
24
+ The conjugate parameter is the parameter that both the likelihood and prior PDF depend on.
25
+
26
+ For more information on conjugacy and conjugate distributions see https://en.wikipedia.org/wiki/Conjugate_prior.
27
+
28
+ For implicit regularized Gaussians see:
29
+
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.
31
+
32
+ """
33
+
34
+ def _initialize(self):
35
+ pass
36
+
37
+ @SamplerNew.target.setter # Overwrite the target setter to set the conjugate pair
38
+ def target(self, value):
39
+ """ Set the target density. Runs validation of the target. """
40
+ self._target = value
41
+ if self._target is not None:
42
+ self._set_conjugatepair()
43
+ self.validate_target()
44
+
45
+ def validate_target(self):
46
+ self._ensure_target_is_posterior()
47
+ self._conjugatepair.validate_target()
48
+
49
+ def step(self):
50
+ self.current_point = self._conjugatepair.sample()
51
+ return 1 # Returns acceptance rate of 1
52
+
53
+ def tune(self, skip_len, update_count):
54
+ pass # No tuning required for conjugate sampler
55
+
56
+ def _ensure_target_is_posterior(self):
57
+ """ Ensure that the target is a Posterior distribution. """
58
+ if not isinstance(self.target, Posterior):
59
+ raise TypeError("Conjugate sampler requires a target of type Posterior")
60
+
61
+ def _set_conjugatepair(self):
62
+ """ Set the conjugate pair based on the likelihood and prior. This requires target to be set. """
63
+ self._ensure_target_is_posterior()
64
+ if isinstance(self.target.likelihood.distribution, (Gaussian, GMRF)) and isinstance(self.target.prior, Gamma):
65
+ self._conjugatepair = _GaussianGammaPair(self.target)
66
+ elif isinstance(self.target.likelihood.distribution, (RegularizedGaussian, RegularizedGMRF)) and isinstance(self.target.prior, Gamma):
67
+ self._conjugatepair = _RegularizedGaussianGammaPair(self.target)
68
+ else:
69
+ raise ValueError(f"Conjugacy is not defined for likelihood {type(self.target.likelihood.distribution)} and prior {type(self.target.prior)}, in CUQIpy")
70
+
71
+ def __repr__(self):
72
+ msg = super().__repr__()
73
+ if hasattr(self, "_conjugatepair"):
74
+ msg += f"\n Conjugate pair:\n\t {type(self._conjugatepair).__name__.removeprefix('_')}"
75
+ return msg
76
+
77
+ class _ConjugatePair(ABC):
78
+ """ Abstract base class for conjugate pairs (likelihood, prior) used in the Conjugate sampler. """
79
+
80
+ def __init__(self, target):
81
+ self.target = target
82
+
83
+ @abstractmethod
84
+ def validate_target(self):
85
+ """ Validate the target distribution for the conjugate pair. """
86
+ pass
87
+
88
+ @abstractmethod
89
+ def sample(self):
90
+ """ Sample from the conjugate distribution. """
91
+ pass
92
+
93
+
94
+ class _GaussianGammaPair(_ConjugatePair):
95
+ """ Implementation for the Gaussian-Gamma conjugate pair."""
96
+
97
+ 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
+ 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):
118
+ # Extract variables
119
+ b = self.target.likelihood.data # mu
120
+ m = len(b) # n
121
+ Ax = self.target.likelihood.distribution.mean # x_i
122
+ L = self.target.likelihood.distribution(np.array([1])).sqrtprec # L
123
+ alpha = self.target.prior.shape # alpha
124
+ beta = self.target.prior.rate # beta
125
+
126
+ # 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()
130
+
131
+
132
+ class _RegularizedGaussianGammaPair(_ConjugatePair):
133
+ """Implementation for the Regularized Gaussian-Gamma conjugate pair."""
134
+
135
+ 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
+ if self.target.prior.dim != 1:
143
+ raise ValueError("Conjugate sampler only works with univariate Gamma prior")
144
+
145
+ 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):
159
+ # Extract variables
160
+ b = self.target.likelihood.data # mu
161
+ m = np.count_nonzero(b) # n
162
+ Ax = self.target.likelihood.distribution.mean # x_i
163
+ L = self.target.likelihood.distribution(np.array([1])).sqrtprec # L
164
+ alpha = self.target.prior.shape # alpha
165
+ beta = self.target.prior.rate # beta
166
+
167
+ # Create Gamma distribution and sample
168
+ dist = Gamma(shape=m/2 + alpha, rate=.5 * np.linalg.norm(L @ (Ax - b))**2 + beta)
169
+
170
+ return dist.sample()
171
+
172
+ def _get_conjugate_parameter(target):
173
+ """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"""
174
+ par_name = target.prior.name
175
+ mutable_likelihood_vars = target.likelihood.distribution.get_mutable_variables()
176
+
177
+ found_parameter_pairs = []
178
+
179
+ for var_key in mutable_likelihood_vars:
180
+ attr = getattr(target.likelihood.distribution, var_key)
181
+ if callable(attr) and par_name in get_non_default_args(attr):
182
+ 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:
188
+ raise ValueError(f"Unable to find conjugate parameter {par_name} in likelihood function for conjugate sampler with target {target}")
189
+
190
+ def _check_conjugate_parameter_is_scalar_identity(f):
191
+ """Tests whether a function (scalar to scalar) is the identity (lambda x: x)."""
192
+ test_values = [1.0, 10.0, 100.0]
193
+ return all(np.allclose(f(x), x) for x in test_values)
194
+
195
+ def _check_conjugate_parameter_is_scalar_reciprocal(f):
196
+ """Tests whether a function (scalar to scalar) is the reciprocal (lambda x : 1.0/x)."""
197
+ return all(math.isclose(f(x), 1.0 / x) for x in [1.0, 10.0, 100.0])
@@ -1,9 +1,10 @@
1
1
  import numpy as np
2
- from cuqi.experimental.mcmc import SamplerNew
3
- from cuqi.distribution import Posterior, LMRF, Gamma
2
+ from cuqi.experimental.mcmc import ConjugateNew
3
+ from cuqi.experimental.mcmc._conjugate import _ConjugatePair, _get_conjugate_parameter, _check_conjugate_parameter_is_scalar_reciprocal
4
+ from cuqi.distribution import LMRF, Gamma
4
5
  import scipy as sp
5
6
 
6
- class ConjugateApproxNew(SamplerNew):
7
+ class ConjugateApproxNew(ConjugateNew):
7
8
  """ Approximate Conjugate sampler
8
9
 
9
10
  Sampler for sampling a posterior distribution where the likelihood and prior can be approximated
@@ -16,26 +17,27 @@ class ConjugateApproxNew(SamplerNew):
16
17
 
17
18
  LMRF likelihood must have zero mean.
18
19
 
19
- Currently, the sampler does NOT automatically check that the conjugate distributions are defined on the correct parameters.
20
-
21
-
22
- For more information on conjugate pairs, see https://en.wikipedia.org/wiki/Conjugate_prior.
20
+ For more details on conjugacy see :class:`ConjugateNew`.
23
21
 
24
22
  """
25
23
 
26
- def _initialize(self):
27
- pass
24
+ def _set_conjugatepair(self):
25
+ """ Set the conjugate pair based on the likelihood and prior. This requires target to be set. """
26
+ if isinstance(self.target.likelihood.distribution, LMRF) and isinstance(self.target.prior, Gamma):
27
+ self._conjugatepair = _LMRFGammaPair(self.target)
28
+ else:
29
+ raise ValueError(f"Conjugacy is not defined for likelihood {type(self.target.likelihood.distribution)} and prior {type(self.target.prior)}, in CUQIpy")
28
30
 
29
- def validate_target(self):
30
31
 
31
- if not isinstance(self.target, Posterior):
32
- raise TypeError("Approximate conjugate sampler requires a target of type Posterior")
32
+ class _LMRFGammaPair(_ConjugatePair):
33
+ """ Implementation of the conjugate pair (LMRF, Gamma) """
33
34
 
35
+ def validate_target(self):
34
36
  if not isinstance(self.target.likelihood.distribution, LMRF):
35
37
  raise ValueError("Approximate conjugate sampler only works with LMRF likelihood function")
36
38
 
37
39
  if not isinstance(self.target.prior, Gamma):
38
- raise ValueError("Approximate conjugate sampler only works with Gamma prior")
40
+ raise ValueError("Approximate conjugate sampler with LMRF likelihood only works with Gamma prior")
39
41
 
40
42
  if not self.target.prior.dim == 1:
41
43
  raise ValueError("Approximate conjugate sampler only works with univariate Gamma prior")
@@ -43,7 +45,14 @@ class ConjugateApproxNew(SamplerNew):
43
45
  if np.sum(self.target.likelihood.distribution.location) != 0:
44
46
  raise ValueError("Approximate conjugate sampler only works with zero mean LMRF likelihood")
45
47
 
46
- def step(self):
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")
54
+
55
+ def sample(self):
47
56
  # Extract variables
48
57
  # Here we approximate the LMRF with a Gaussian
49
58
 
@@ -69,7 +78,4 @@ class ConjugateApproxNew(SamplerNew):
69
78
  # Create Gamma distribution and sample
70
79
  dist = Gamma(shape=d+alpha, rate=np.linalg.norm(Lx)**2+beta)
71
80
 
72
- self.current_point = dist.sample()
73
-
74
- def tune(self, skip_len, update_count):
75
- pass
81
+ return dist.sample()
@@ -23,6 +23,7 @@ class DirectNew(SamplerNew):
23
23
 
24
24
  def step(self):
25
25
  self.current_point = self.target.sample()
26
+ return 1 # Returns acceptance rate of 1
26
27
 
27
28
  def tune(self, skip_len, update_count):
28
29
  pass
@@ -710,8 +710,7 @@ class BayesianProblem(object):
710
710
  # Require gradient?
711
711
  if must_have_gradient:
712
712
  try:
713
- posterior.prior.gradient(np.zeros(posterior.prior.dim))
714
- posterior.likelihood.gradient(np.zeros(posterior.likelihood.dim))
713
+ posterior.posterior.gradient(np.zeros(posterior.posterior.dim))
715
714
  G = True
716
715
  except (NotImplementedError, AttributeError):
717
716
  G = False
@@ -1,77 +0,0 @@
1
- import numpy as np
2
- from cuqi.experimental.mcmc import SamplerNew
3
- from cuqi.distribution import Posterior, Gaussian, Gamma, GMRF
4
- from cuqi.implicitprior import RegularizedGaussian, RegularizedGMRF
5
-
6
- class ConjugateNew(SamplerNew):
7
- """ Conjugate sampler
8
-
9
- Sampler for sampling a posterior distribution where the likelihood and prior are conjugate.
10
-
11
- Currently supported conjugate pairs are:
12
- - (Gaussian, Gamma) where Gamma is defined on the precision parameter of the Gaussian
13
- - (GMRF, Gamma) where Gamma is defined on the precision parameter of the GMRF
14
- - (RegularizedGaussian, Gamma) with nonnegativity constraints only and Gamma is defined on the precision parameter of the RegularizedGaussian
15
- - (RegularizedGMRF, Gamma) with nonnegativity constraints only and Gamma is defined on the precision parameter of the RegularizedGMRF
16
-
17
- Gamma distribution must be univariate.
18
-
19
- Currently, the sampler does NOT automatically check that the conjugate distributions are defined on the correct parameters.
20
-
21
- For more information on conjugate pairs, see https://en.wikipedia.org/wiki/Conjugate_prior.
22
-
23
- For implicit regularized Gaussians see:
24
-
25
- [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.
26
-
27
- """
28
- def _initialize(self):
29
- pass
30
-
31
- def validate_target(self):
32
-
33
- if not isinstance(self.target, Posterior):
34
- raise TypeError("Conjugate sampler requires a target of type Posterior")
35
-
36
- if not isinstance(self.target.likelihood.distribution, (Gaussian, GMRF, RegularizedGaussian, RegularizedGMRF)):
37
- raise ValueError("Conjugate sampler only works with a Gaussian-type likelihood function")
38
-
39
- if not isinstance(self.target.prior, Gamma):
40
- raise ValueError("Conjugate sampler only works with Gamma prior")
41
-
42
- if not self.target.prior.dim == 1:
43
- raise ValueError("Conjugate sampler only works with univariate Gamma prior")
44
-
45
- if isinstance(self.target.likelihood.distribution, (RegularizedGaussian, RegularizedGMRF)) and self.target.likelihood.distribution.preset not in ["nonnegativity"]:
46
- raise ValueError("Conjugate sampler only works with implicit regularized Gaussian likelihood with nonnegativity constraints")
47
-
48
- def step(self):
49
- # Extract variables
50
- b = self.target.likelihood.data #mu
51
- m = self._calc_m_for_Gaussians(b) #n
52
- Ax = self.target.likelihood.distribution.mean #x_i
53
- L = self.target.likelihood.distribution(np.array([1])).sqrtprec #L
54
- alpha = self.target.prior.shape #alpha
55
- beta = self.target.prior.rate #beta
56
-
57
- # Create Gamma distribution and sample
58
- dist = Gamma(shape=m/2+alpha,rate=.5*np.linalg.norm(L@(Ax-b))**2+beta)
59
-
60
- self.current_point = dist.sample()
61
-
62
- def tune(self, skip_len, update_count):
63
- pass
64
-
65
- def _calc_m_for_Gaussians(self, b):
66
- """ Helper method to calculate m parameter for Gaussian-Gamma conjugate pair.
67
-
68
- Classically m defines the number of observations in the Gaussian likelihood function.
69
-
70
- However, for implicit regularized Gaussians, m is the number of non-zero elements in the data vector b see [1].
71
-
72
- """
73
-
74
- if isinstance(self.target.likelihood.distribution, (Gaussian, GMRF)):
75
- return len(b)
76
- elif isinstance(self.target.likelihood.distribution, (RegularizedGaussian, RegularizedGMRF)):
77
- return np.count_nonzero(b)