CUQIpy 1.0.0.post0.dev337__py3-none-any.whl → 1.0.0.post0.dev360__py3-none-any.whl

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

Potentially problematic release.


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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: CUQIpy
3
- Version: 1.0.0.post0.dev337
3
+ Version: 1.0.0.post0.dev360
4
4
  Summary: Computational Uncertainty Quantification for Inverse problems in Python
5
5
  Maintainer-email: "Nicolai A. B. Riis" <nabr@dtu.dk>, "Jakob S. Jørgensen" <jakj@dtu.dk>, "Amal M. Alghamdi" <amaal@dtu.dk>, Chao Zhang <chaz@dtu.dk>
6
6
  License: Apache License
@@ -1,6 +1,6 @@
1
1
  cuqi/__init__.py,sha256=LsGilhl-hBLEn6Glt8S_l0OJzAA1sKit_rui8h-D-p0,488
2
2
  cuqi/_messages.py,sha256=fzEBrZT2kbmfecBBPm7spVu7yHdxGARQB4QzXhJbCJ0,415
3
- cuqi/_version.py,sha256=bop6laBkqMl8sVGPBD_LKMsjPG6Dv1XBkb3SMN2t8KY,510
3
+ cuqi/_version.py,sha256=cS-WO8xKjlj6I1M1dMYpxgIX4PiSHuwIh3aCZqcJy_8,510
4
4
  cuqi/config.py,sha256=wcYvz19wkeKW2EKCGIKJiTpWt5kdaxyt4imyRkvtTRA,526
5
5
  cuqi/diagnostics.py,sha256=5OrbJeqpynqRXOe5MtOKKhe7EAVdOEpHIqHnlMW9G_c,3029
6
6
  cuqi/array/__init__.py,sha256=-EeiaiWGNsE3twRS4dD814BIlfxEsNkTCZUc5gjOXb0,30
@@ -14,7 +14,7 @@ cuqi/data/cookie.png,sha256=mr6wUeoIUc5VC2qYj8vafOmTbcRwz0fHz4IIPK9_PnE,984680
14
14
  cuqi/data/satellite.mat,sha256=a0Nz_Ak-Y0m360dH74pa_rpk-MhaQ91ftGTKhQX7I8g,16373
15
15
  cuqi/density/__init__.py,sha256=0zfVcPgqdqiPkss5n_WP_PUt-G3ovHXjokhqEKIlLwA,48
16
16
  cuqi/density/_density.py,sha256=BG7gtP0cbFYLVgjYQGkNAhM95PR5ocBVLKRlOVX2PyM,7253
17
- cuqi/distribution/__init__.py,sha256=f85DzOHPvGec9nr_AIfp_THSuC4WN8ZUJMSLZrKClG8,615
17
+ cuqi/distribution/__init__.py,sha256=qKt5uV5E988Zj3LIz0mgk3yICWKY2uoOq4vqLCETrdg,667
18
18
  cuqi/distribution/_beta.py,sha256=hdAc6Tbuz9Yqf76NSHxpaUgN7s6Z2lNV7YSRD3JhyCU,2997
19
19
  cuqi/distribution/_cauchy.py,sha256=UsVXYz8HhagXN5fIWSAIyELqhsJAX_-wk9kkRGgRmA8,3296
20
20
  cuqi/distribution/_cmrf.py,sha256=tCbEulM_O7FB3C_W-3IqZp9zGHkTofCdFF0ybHc9UZI,3745
@@ -28,23 +28,25 @@ cuqi/distribution/_joint_distribution.py,sha256=jRsV1Dt-pW6sG_xNqF0TugeVKDJY4Kh5
28
28
  cuqi/distribution/_laplace.py,sha256=5exLvlzJm2AgfvZ3KUSkjfwlGwwbsktBxP8z0iLMik8,1401
29
29
  cuqi/distribution/_lmrf.py,sha256=rdGoQ-fPe1oW6Z29P-l3woq0NX3_RxUQ2rzm1VzemNM,3290
30
30
  cuqi/distribution/_lognormal.py,sha256=st1Uhf67qy2Seo65hA88JQ7lkEjQkW6KxznXahF_0SU,2844
31
+ cuqi/distribution/_modifiedhalfnormal.py,sha256=gB9fj10hdMvr05aa_xhAirJATLqKh1gpbdSs2pRoAVM,7267
31
32
  cuqi/distribution/_normal.py,sha256=UeoTtGDT7YSf4ZNo2amlVF9K-YQpYbf8q76jcRJTVFw,1914
32
33
  cuqi/distribution/_posterior.py,sha256=zAfL0GECxekZ2lBt1W6_LN0U_xskMwK4VNce5xAF7ig,5018
33
34
  cuqi/distribution/_uniform.py,sha256=7xJmCZH_LPhuGkwEDGh-_CTtzcWKrXMOxtTJUFb7Ydo,1607
34
35
  cuqi/experimental/__init__.py,sha256=vhZvyMX6rl8Y0haqCzGLPz6PSUKyu75XMQbeDHqTTrw,83
35
- cuqi/experimental/mcmc/__init__.py,sha256=TyN_CASRnTcUBQYxoYUeSUBhorLzVwgPQaY72-6hXm8,537
36
+ cuqi/experimental/mcmc/__init__.py,sha256=0Vk_MzfE_9tvqQRgR6_3nkjSe_D3vgFqVM9pFrXN2iQ,581
36
37
  cuqi/experimental/mcmc/_conjugate.py,sha256=qYrBvZ9wNK4oBz0c0RRUtQkbpPIHI3BvBYSLRw8ok5k,3757
37
38
  cuqi/experimental/mcmc/_conjugate_approx.py,sha256=JQe9gmnNespCxSP6vaZWfizFvUWUh8Jn-jRqsJYyNeM,2839
38
- cuqi/experimental/mcmc/_cwmh.py,sha256=dbvmy6Fyr_xszbO3YmYcRsDaRRtQfPimLo6rbp0II6M,6898
39
+ cuqi/experimental/mcmc/_cwmh.py,sha256=-TM_S_UtD5ljEfXGEUpYImxNx3JXppIKTSpoWen7kP8,7142
39
40
  cuqi/experimental/mcmc/_direct.py,sha256=E3UevdJ_DLk2wL0lid1TTKkdmgnIMJ5Ihr7iM1jU8KI,738
40
41
  cuqi/experimental/mcmc/_gibbs.py,sha256=z6YOCiBM1YuZbQHfdmsArR-pT61dsS14F_O4kUxsNYM,10638
41
42
  cuqi/experimental/mcmc/_hmc.py,sha256=0sZMHtnNFGGtQdzpx-cgqA0xyfvGy7r4K62RH3AQNa4,19285
42
43
  cuqi/experimental/mcmc/_langevin_algorithm.py,sha256=n6WRQooKuUDjmqF-CtpcSNFDvaHCgLKhWxX-hi7h_ZA,8224
43
44
  cuqi/experimental/mcmc/_laplace_approximation.py,sha256=VVLOKQWZViT1CZg5RDiycG6trpKdQg94aQCKrAdSl2g,5707
44
- cuqi/experimental/mcmc/_mh.py,sha256=6yQjdpx3fMKvescRFL94Ik-ALTKi0BwBRXbTsIsck-I,2630
45
+ cuqi/experimental/mcmc/_mh.py,sha256=r1RNQLuOaT_WlHvCwS2StsgNAOeTD36z2fOzUtbGhHk,2842
45
46
  cuqi/experimental/mcmc/_pcn.py,sha256=T4T32mfoii3k6Jfz0qxPQbwdh6wdVOxttiEP7NWaZzg,3386
46
47
  cuqi/experimental/mcmc/_rto.py,sha256=wzlqm8waT6mB-3RFtMz-PlSUa1Yy3dfUoyKKahTaey4,10086
47
48
  cuqi/experimental/mcmc/_sampler.py,sha256=4dh9XVALkD3Ro9vkLZkNoFNge7Xv8QRbjh4LB7NY0HI,20073
49
+ cuqi/experimental/mcmc/_utilities.py,sha256=qrSq8hvK_G2rSxYYfR5psk4sbTe7098TS5QNu13THeE,528
48
50
  cuqi/geometry/__init__.py,sha256=Tz1WGzZBY-QGH3c0GiyKm9XHN8MGGcnU6TUHLZkzB3o,842
49
51
  cuqi/geometry/_geometry.py,sha256=WYFC-4_VBTW73b2ldsnfGYKvdSiCE8plr89xTSmkadg,46804
50
52
  cuqi/implicitprior/__init__.py,sha256=ZRZ9fgxgEl5n0A9F7WCl1_jid-GUiC8ZLkyTmGQmFlY,100
@@ -81,8 +83,8 @@ cuqi/testproblem/_testproblem.py,sha256=x769LwwRdJdzIiZkcQUGb_5-vynNTNALXWKato7s
81
83
  cuqi/utilities/__init__.py,sha256=T4tLsC215MknBCsw_C0Qeeg_ox26aDUrCA5hbWvNQkU,387
82
84
  cuqi/utilities/_get_python_variable_name.py,sha256=QwlBVj2koJRA8s8pWd554p7-ElcI7HUwY32HknaR92E,1827
83
85
  cuqi/utilities/_utilities.py,sha256=MWAqV6L5btMpWwlUzrZYuV2VeSpfTbOaLRMRkuw2WIA,8509
84
- CUQIpy-1.0.0.post0.dev337.dist-info/LICENSE,sha256=kJWRPrtRoQoZGXyyvu50Uc91X6_0XRaVfT0YZssicys,10799
85
- CUQIpy-1.0.0.post0.dev337.dist-info/METADATA,sha256=d44JgxdIjhKP_Z33mhZ3vll3JbJrLsxAIIIpFh6N6WU,18393
86
- CUQIpy-1.0.0.post0.dev337.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
87
- CUQIpy-1.0.0.post0.dev337.dist-info/top_level.txt,sha256=AgmgMc6TKfPPqbjV0kvAoCBN334i_Lwwojc7HE3ZwD0,5
88
- CUQIpy-1.0.0.post0.dev337.dist-info/RECORD,,
86
+ CUQIpy-1.0.0.post0.dev360.dist-info/LICENSE,sha256=kJWRPrtRoQoZGXyyvu50Uc91X6_0XRaVfT0YZssicys,10799
87
+ CUQIpy-1.0.0.post0.dev360.dist-info/METADATA,sha256=zYqabNMLjZdQ55Z_bQyptCZ2QV-N5FLCF7G05No2jK8,18393
88
+ CUQIpy-1.0.0.post0.dev360.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
89
+ CUQIpy-1.0.0.post0.dev360.dist-info/top_level.txt,sha256=AgmgMc6TKfPPqbjV0kvAoCBN334i_Lwwojc7HE3ZwD0,5
90
+ CUQIpy-1.0.0.post0.dev360.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (70.1.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
cuqi/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-06-11T10:55:36+0200",
11
+ "date": "2024-07-01T14:33:09+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "b3c53f2ac21c807186dc35053421615c1cb3f205",
15
- "version": "1.0.0.post0.dev337"
14
+ "full-revisionid": "4da44104c9163182cd1eeb31fb4678a2893111fe",
15
+ "version": "1.0.0.post0.dev360"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -3,6 +3,7 @@ from ._beta import Beta
3
3
  from ._cauchy import Cauchy
4
4
  from ._cmrf import CMRF
5
5
  from ._gamma import Gamma
6
+ from ._modifiedhalfnormal import ModifiedHalfNormal
6
7
  from ._gaussian import Gaussian, JointGaussianSqrtPrec
7
8
  from ._gmrf import GMRF
8
9
  from ._inverse_gamma import InverseGamma
@@ -0,0 +1,184 @@
1
+ import numpy as np
2
+ import scipy.stats as sps
3
+ import scipy.special as special
4
+ from cuqi.distribution import Distribution
5
+ from cuqi.utilities import force_ndarray
6
+
7
+ class ModifiedHalfNormal(Distribution):
8
+ """
9
+ Represents a modified half-normal (MHN) distribution, a three-parameter family of distributions generalizing the Gamma distribution.
10
+ The distribution is continuous with pdf
11
+ f(x; alpha, beta, gamma) propto x^(alpha-1) * exp(-beta * x^2 + gamma * x)
12
+
13
+ The MHN generalizes the half-normal distribution, because
14
+ f(x; 1, beta, 0) propto exp(-beta * x^2)
15
+
16
+ The MHN generalizes the gamma distribution because
17
+ f(x; alpha, 0, -gamma) propto x^(alpha-1) * exp(- gamma * x)
18
+
19
+ Reference:
20
+ [1] Sun, et al. "The Modified-Half-Normal distribution: Properties and an efficient sampling scheme." Communications in Statistics-Theory and Methods
21
+
22
+ Parameters
23
+ ----------
24
+ alpha : float
25
+ The polynomial exponent parameter of the MHN distribution. Must be positive.
26
+
27
+ beta : float
28
+ The quadratic exponential parameter of the MHN distribution. Must be positive.
29
+
30
+ gamma : float
31
+ The linear exponential parameter of the MHN distribution.
32
+
33
+ """
34
+ def __init__(self, alpha=None, beta=None, gamma=None, is_symmetric=False, **kwargs):
35
+ # Init from abstract distribution class
36
+ super().__init__(is_symmetric=is_symmetric, **kwargs)
37
+
38
+ self._alpha = alpha
39
+ self._beta = beta
40
+ self._gamma = gamma
41
+
42
+ @property
43
+ def alpha(self):
44
+ """ The polynomial exponent parameter of the MHN distribution. Must be positive. """
45
+ return self._alpha
46
+
47
+ @alpha.setter
48
+ def shape(self, value):
49
+ self._shape = force_ndarray(value, flatten=True)
50
+
51
+ @property
52
+ def beta(self):
53
+ """ The quadratic exponential parameter of the MHN distribution. Must be positive. """
54
+ return self._alpha
55
+
56
+ @beta.setter
57
+ def beta(self, value):
58
+ self._beta = force_ndarray(value, flatten=True)
59
+
60
+ @property
61
+ def gamma(self):
62
+ """ The linear exponential parameter of the MHN distribution. """
63
+ return self._alpha
64
+
65
+ @gamma.setter
66
+ def gamma(self, value):
67
+ self._gamma = force_ndarray(value, flatten=True)
68
+
69
+ def logpdf(self, x): # Unnormalized
70
+ return np.sum((self.alpha - 1)*np.log(x) - self.beta * x * x + self.gamma * x)
71
+
72
+ def _gradient_scalar(self, val):
73
+ if val <= 0.0:
74
+ return np.nan
75
+ return (self.alpha - 1)/val - 2*self.beta*val + self.gamma
76
+
77
+ def _gradient(self, val, *args, **kwargs):
78
+ if hasattr(self.alpha, '__iter__'):
79
+ return np.array([self._gradient_scalar(v) for v in val])
80
+ else:
81
+ return np.array([self.dim*[self._gradient_scalar(v)] for v in val])
82
+
83
+ def _MHN_sample_gamma_proposal(self, alpha, beta, gamma, rng, delta=None):
84
+ """
85
+ Sample from a modified half-normal distribution using a Gamma distribution proposal.
86
+ """
87
+ if delta is None:
88
+ delta = beta + (gamma*gamma - gamma*np.sqrt(gamma*gamma + 8*beta*alpha))/(4*alpha)
89
+
90
+ while True:
91
+ T = rng.gamma(alpha/2, 1.0/delta)
92
+ X = np.sqrt(T)
93
+ U = rng.uniform()
94
+ if X > 0 and np.log(U) < -(beta-delta)*T + gamma*X - gamma*gamma/(4*(beta-delta)):
95
+ return X
96
+
97
+ def _MHN_sample_normal_proposal(self, alpha, beta, gamma, mu, rng):
98
+ """
99
+ Sample from a modified half-normal distribution using a Normal/Gaussian distribution proposal.
100
+ """
101
+ if mu is None:
102
+ mu = (gamma + np.sqrt(gamma*gamma + 8*beta*(alpha - 1)))/(4*beta)
103
+
104
+ while True:
105
+ X = rng.normal(mu, np.sqrt(0.5/beta))
106
+ U = rng.uniform()
107
+ if X > 0 and np.log(U) < (alpha-1)*np.log(X) - np.log(mu) + (2*beta*mu-gamma)*(mu-X):
108
+ return X
109
+
110
+ def _MHN_sample_positive_gamma_1(self, alpha, beta, gamma, rng):
111
+ """
112
+ Sample from a modified half-normal distribution, assuming alpha is greater than one and gamma is positive.
113
+ """
114
+ if gamma <= 0.0:
115
+ raise ValueError("gamma needs to be positive")
116
+
117
+ if alpha <= 1.0:
118
+ raise ValueError("alpha needs to be greater than 1.0")
119
+
120
+ # Decide whether to use Normal or sqrt(Gamma) proposals for acceptance-rejectance scheme
121
+ mu = (gamma + np.sqrt(gamma*gamma + 8*beta*(alpha - 1)))/(4*beta)
122
+ K1 = 2*np.sqrt(np.pi)
123
+ K1 *= np.power((np.sqrt(beta)*(alpha-1))/(2*beta*mu-gamma), alpha - 1)
124
+ K1 *= np.exp(-(alpha-1)+beta*mu*mu)
125
+
126
+ delta = beta + (gamma*gamma - gamma*np.sqrt(gamma*gamma + 8*beta*alpha))/(4*alpha)
127
+ K2 = np.power(beta/delta, 0.5*alpha)
128
+ K2 *= special.gamma(alpha/2.0)
129
+ K2 *= np.exp(gamma*gamma/(4*(beta-delta)))
130
+
131
+ if K2 > K1: # Use normal proposal
132
+ return self._MHN_sample_normal_proposal(alpha, beta, gamma, mu, rng)
133
+ else: # Use sqrt(gamma) proposal
134
+ return self._MHN_sample_gamma_proposal(alpha, beta, gamma, rng, delta)
135
+
136
+ def _MHN_sample_negative_gamma(self, alpha, beta, gamma, rng, m=None):
137
+ """
138
+ Sample from a modified half-normal distribution, assuming gamma is negative.
139
+ The argument 'm' is the matching point, see Algorithm 3 from [1] for details.
140
+ """
141
+ if gamma > 0.0:
142
+ raise ValueError("gamma needs to be negative")
143
+
144
+ if m is None:
145
+ if alpha <= 1.0:
146
+ m = 1.0
147
+ else:
148
+ m = "mode"
149
+
150
+ # The acceptance rate of this choice is at least 0.5*sqrt(2) approx 70.7 percent, according to Theorem 4 from [1].
151
+ if isinstance(m, str) and m.lower() == "mode":
152
+ m = (gamma + np.sqrt(gamma*gamma + 8*beta*alpha))/(4*beta)
153
+
154
+ while True:
155
+ val1 = (beta*m-gamma)/(2*beta*m-gamma)
156
+ val2 = m*(beta*m-gamma)
157
+ T = rng.gamma(alpha*val1, 1.0/val2)
158
+ X = m*np.power(T,val1)
159
+ U = rng.uniform()
160
+ if np.log(U) < val2*T-beta*X*X+gamma*X:
161
+ return X
162
+
163
+ def _MHN_sample(self, alpha, beta, gamma, m=None, rng=None):
164
+ """
165
+ Sample from a modified half-normal distribution using an algorithm from [1].
166
+ """
167
+ if rng == None:
168
+ rng = np.random
169
+
170
+ if gamma <= 0.0:
171
+ return self._MHN_sample_negative_gamma(alpha, beta, gamma, m=m, rng=rng)
172
+
173
+ if alpha > 1:
174
+ return self._MHN_sample_positive_gamma_1(alpha, beta, gamma, rng=rng)
175
+
176
+ return self._MHN_sample_gamma_proposal(alpha, beta, gamma, rng=rng)
177
+
178
+ def _sample(self, N, rng=None):
179
+ if hasattr(self.alpha, '__getitem__'):
180
+ return np.array([self._MHN_sample(self.alpha[i], self.beta[i], self.gamma[i], rng=rng) for i in range(N)])
181
+ else:
182
+ return np.array([self._MHN_sample(self.alpha, self.beta, self.gamma, rng=rng) for i in range(N)])
183
+
184
+
@@ -12,3 +12,4 @@ from ._gibbs import HybridGibbsNew
12
12
  from ._conjugate import ConjugateNew
13
13
  from ._conjugate_approx import ConjugateApproxNew
14
14
  from ._direct import DirectNew
15
+ from ._utilities import find_valid_samplers
@@ -100,6 +100,9 @@ class CWMHNew(ProposalBasedSamplerNew):
100
100
  raise ValueError(
101
101
  "Target should be an instance of "+\
102
102
  f"{cuqi.density.Density.__class__.__name__}")
103
+ # Fail when there is no log density, which is currently assumed to be the case in case NaN is returned.
104
+ if np.isnan(self.target.logd(self._default_initial_point)):
105
+ raise ValueError("Target does not have valid logd")
103
106
 
104
107
  def validate_proposal(self):
105
108
  if not isinstance(self.proposal, cuqi.distribution.Distribution):
@@ -32,7 +32,9 @@ class MHNew(ProposalBasedSamplerNew):
32
32
  self._scale_temp = self.scale
33
33
 
34
34
  def validate_target(self):
35
- pass # All targets are valid
35
+ # Fail only when there is no log density, which is currently assumed to be the case in case NaN is returned.
36
+ if np.isnan(self.target.logd(self._default_initial_point)):
37
+ raise ValueError("Target does not have valid logd")
36
38
 
37
39
  def validate_proposal(self):
38
40
  if not isinstance(self.proposal, cuqi.distribution.Distribution):
@@ -0,0 +1,17 @@
1
+ import cuqi
2
+ import inspect
3
+
4
+ def find_valid_samplers(target):
5
+ """ Finds all samplers in the cuqi.experimental.mcmc module that accept the provided target. """
6
+
7
+ all_samplers = [(name, cls) for name, cls in inspect.getmembers(cuqi.experimental.mcmc, inspect.isclass) if issubclass(cls, cuqi.experimental.mcmc.SamplerNew)]
8
+ valid_samplers = []
9
+
10
+ for name, sampler in all_samplers:
11
+ try:
12
+ sampler(target)
13
+ valid_samplers += [name]
14
+ except:
15
+ pass
16
+
17
+ return valid_samplers