CUQIpy 1.0.0.post0.dev57__py3-none-any.whl → 1.0.0.post0.dev91__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.dev57
3
+ Version: 1.0.0.post0.dev91
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
@@ -202,7 +202,7 @@ Description-Content-Type: text/markdown
202
202
  License-File: LICENSE
203
203
  Requires-Dist: matplotlib
204
204
  Requires-Dist: numpy >=1.17.0
205
- Requires-Dist: scipy
205
+ Requires-Dist: scipy <1.13
206
206
  Requires-Dist: arviz
207
207
 
208
208
  <div align="center">
@@ -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=XsNmdXuVtv7ZVDfdiVhJxoQFSekl2dvzcBYP7w0II3U,509
3
+ cuqi/_version.py,sha256=wjkb-AlAnGV3_a5XYx4H4xHteXak-c9qRWEs8JAhr3k,509
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
@@ -32,7 +32,8 @@ cuqi/distribution/_normal.py,sha256=UeoTtGDT7YSf4ZNo2amlVF9K-YQpYbf8q76jcRJTVFw,
32
32
  cuqi/distribution/_posterior.py,sha256=zAfL0GECxekZ2lBt1W6_LN0U_xskMwK4VNce5xAF7ig,5018
33
33
  cuqi/distribution/_uniform.py,sha256=7xJmCZH_LPhuGkwEDGh-_CTtzcWKrXMOxtTJUFb7Ydo,1607
34
34
  cuqi/experimental/__init__.py,sha256=vhZvyMX6rl8Y0haqCzGLPz6PSUKyu75XMQbeDHqTTrw,83
35
- cuqi/experimental/mcmc/__init__.py,sha256=RSS6rO6rT_kPSwLDXNP-HU7WQKYMd6ij_qt42VOmQqA,231
35
+ cuqi/experimental/mcmc/__init__.py,sha256=MV1lHnRJTBsJMYEywnEx4iTEEAvN1SLIXhpRU_eiX-8,258
36
+ cuqi/experimental/mcmc/_cwmh.py,sha256=G-8YjMqPraZm1Pm3n6scFkpa65gdtI1WTQxlL21etEI,8066
36
37
  cuqi/experimental/mcmc/_langevin_algorithm.py,sha256=ckVHDXLaw8hsUaOAFAEs7bL2Ny7W1QBKSc4AAC-TCis,9986
37
38
  cuqi/experimental/mcmc/_mh.py,sha256=AslackZJ3hPUNQfy70Fh9WoRPtcsHGqulI0LQgGHBus,3477
38
39
  cuqi/experimental/mcmc/_pcn.py,sha256=ma3pFqFgOmE7woZ41B5CGccKEuaacJPTmKvSEQhvtzs,3981
@@ -65,7 +66,7 @@ cuqi/sampler/_pcn.py,sha256=F0h9-nUFtkqn-o-1s8BCsmr8V7u6R7ycoCOeeV1uhj0,8601
65
66
  cuqi/sampler/_rto.py,sha256=-AtMiYq4fh7pF9zVqfYjYtQbIIEGayrWyRGTj8KecfE,11518
66
67
  cuqi/sampler/_sampler.py,sha256=TkZ_WAS-5Q43oICa-Elc2gftsRTBd7PEDUMDZ9tTGmU,5712
67
68
  cuqi/samples/__init__.py,sha256=E7B9IBUsiOBr-HAKH9o3_Lqhq4KeWO87hmesHkdbwTY,30
68
- cuqi/samples/_samples.py,sha256=6xXT_OmBmtlS6ucTHd57FmRo1gwHEaVuN3p8Llhj4KM,34640
69
+ cuqi/samples/_samples.py,sha256=UUyAhADdkshZZ-wi1ICl3u7FYx1OU5NXgO-EeAS5TCc,34804
69
70
  cuqi/solver/__init__.py,sha256=DGl8IdUnochRXHNDEy_13o_VT0vLFY6FjMmmSH6YUkY,169
70
71
  cuqi/solver/_solver.py,sha256=TgezixCVf8nKGtEF9ZrkaTtAfxSs1Z8CR_cmhdTMqRw,22776
71
72
  cuqi/testproblem/__init__.py,sha256=DWTOcyuNHMbhEuuWlY5CkYkNDSAqhvsKmJXBLivyblU,202
@@ -73,8 +74,8 @@ cuqi/testproblem/_testproblem.py,sha256=x769LwwRdJdzIiZkcQUGb_5-vynNTNALXWKato7s
73
74
  cuqi/utilities/__init__.py,sha256=EfxHLdsyDNugbmbzs43nV_AeKcycM9sVBjG9WZydagA,351
74
75
  cuqi/utilities/_get_python_variable_name.py,sha256=QwlBVj2koJRA8s8pWd554p7-ElcI7HUwY32HknaR92E,1827
75
76
  cuqi/utilities/_utilities.py,sha256=At3DOXRdF3GwLkVcM2FXooGyjAGfPkIM0bRzhTfLmWk,8046
76
- CUQIpy-1.0.0.post0.dev57.dist-info/LICENSE,sha256=kJWRPrtRoQoZGXyyvu50Uc91X6_0XRaVfT0YZssicys,10799
77
- CUQIpy-1.0.0.post0.dev57.dist-info/METADATA,sha256=svDfpVBkUyYbdBo03Tj81BjwNt533tVFAE7NdD7WZZE,18386
78
- CUQIpy-1.0.0.post0.dev57.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
79
- CUQIpy-1.0.0.post0.dev57.dist-info/top_level.txt,sha256=AgmgMc6TKfPPqbjV0kvAoCBN334i_Lwwojc7HE3ZwD0,5
80
- CUQIpy-1.0.0.post0.dev57.dist-info/RECORD,,
77
+ CUQIpy-1.0.0.post0.dev91.dist-info/LICENSE,sha256=kJWRPrtRoQoZGXyyvu50Uc91X6_0XRaVfT0YZssicys,10799
78
+ CUQIpy-1.0.0.post0.dev91.dist-info/METADATA,sha256=KG8D1oXbdnsjU-xCHTLbwmzUCXkndGBNZ9-xGP6XguI,18392
79
+ CUQIpy-1.0.0.post0.dev91.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
80
+ CUQIpy-1.0.0.post0.dev91.dist-info/top_level.txt,sha256=AgmgMc6TKfPPqbjV0kvAoCBN334i_Lwwojc7HE3ZwD0,5
81
+ CUQIpy-1.0.0.post0.dev91.dist-info/RECORD,,
cuqi/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-03-13T21:35:58+0100",
11
+ "date": "2024-04-04T16:40:53+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "cf26d627541ac5ec0cb59b9982f4de36701ce0ac",
15
- "version": "1.0.0.post0.dev57"
14
+ "full-revisionid": "91a1edb3f1ae9412a27489f3faa9a4a9c8ff8a4a",
15
+ "version": "1.0.0.post0.dev91"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -4,3 +4,4 @@ from ._sampler import SamplerNew, ProposalBasedSamplerNew
4
4
  from ._langevin_algorithm import ULANew, MALANew
5
5
  from ._mh import MHNew
6
6
  from ._pcn import pCNNew
7
+ from ._cwmh import CWMHNew
@@ -0,0 +1,221 @@
1
+ import numpy as np
2
+ import cuqi
3
+ from cuqi.experimental.mcmc import ProposalBasedSamplerNew
4
+ from cuqi.array import CUQIarray
5
+ from numbers import Number
6
+
7
+ class CWMHNew(ProposalBasedSamplerNew):
8
+ """Component-wise Metropolis Hastings sampler.
9
+
10
+ Allows sampling of a target distribution by a component-wise random-walk
11
+ sampling of a proposal distribution along with an accept/reject step.
12
+
13
+ Parameters
14
+ ----------
15
+
16
+ target : `cuqi.distribution.Distribution` or lambda function
17
+ The target distribution to sample. Custom logpdfs are supported by using
18
+ a :class:`cuqi.distribution.UserDefinedDistribution`.
19
+
20
+ proposal : `cuqi.distribution.Distribution` or callable method
21
+ The proposal to sample from. If a callable method it should provide a
22
+ single independent sample from proposal distribution. Defaults to a
23
+ Gaussian proposal. *Optional*.
24
+
25
+ scale : float or ndarray
26
+ Scale parameter used to define correlation between previous and proposed
27
+ sample in random-walk. *Optional*. If float, the same scale is used for
28
+ all dimensions. If ndarray, a (possibly) different scale is used for
29
+ each dimension.
30
+
31
+ initial_point : ndarray
32
+ Initial parameters. *Optional*
33
+
34
+ callback : callable, *Optional*
35
+ If set this function will be called after every sample.
36
+ The signature of the callback function is
37
+ `callback(sample, sample_index)`, where `sample` is the current sample
38
+ and `sample_index` is the index of the sample.
39
+ An example is shown in demos/demo31_callback.py.
40
+
41
+ kwargs : dict
42
+ Additional keyword arguments to be passed to the base class
43
+ :class:`ProposalBasedSamplerNew`.
44
+
45
+ Example
46
+ -------
47
+ .. code-block:: python
48
+ import numpy as np
49
+ import cuqi
50
+ # Parameters
51
+ dim = 5 # Dimension of distribution
52
+ mu = np.arange(dim) # Mean of Gaussian
53
+ std = 1 # standard deviation of Gaussian
54
+
55
+ # Logpdf function
56
+ logpdf_func = lambda x: -1/(std**2)*np.sum((x-mu)**2)
57
+
58
+ # Define distribution from logpdf as UserDefinedDistribution (sample
59
+ # and gradients also supported as inputs to UserDefinedDistribution)
60
+ target = cuqi.distribution.UserDefinedDistribution(
61
+ dim=dim, logpdf_func=logpdf_func)
62
+
63
+ # Set up sampler
64
+ sampler = cuqi.experimental.mcmc.CWMHNew(target, scale=1)
65
+
66
+ # Sample
67
+ samples = sampler.sample(2000).get_samples()
68
+
69
+ """
70
+ def __init__(self, target: cuqi.density.Density, proposal=None, scale=1,
71
+ initial_point=None, **kwargs):
72
+ super().__init__(target, proposal=proposal, scale=scale,
73
+ initial_point=initial_point, **kwargs)
74
+
75
+ # set initial scale
76
+ self.scale = scale
77
+
78
+ # set initial acceptance rate
79
+ self._acc = [np.ones((self.dim))]
80
+
81
+ @property
82
+ def scale(self):
83
+ """ Get the scale parameter. """
84
+ return self._scale
85
+
86
+ @scale.setter
87
+ def scale(self, value):
88
+ """ Set the scale parameter. """
89
+ if isinstance(value, Number):
90
+ self._scale = np.ones(self.dim)*value
91
+ elif isinstance(value, np.ndarray):
92
+ self._scale = value
93
+ self._scale_temp = self._scale.copy()
94
+
95
+ def validate_target(self):
96
+ if not isinstance(self.target, cuqi.density.Density):
97
+ raise ValueError(
98
+ "Target should be an instance of "+\
99
+ f"{cuqi.density.Density.__class__.__name__}")
100
+
101
+ @ProposalBasedSamplerNew.proposal.setter
102
+ # TODO. Check if we can refactor this.
103
+ # We can work with a validate_proposal method instead?
104
+ def proposal(self, value):
105
+ fail_msg = "Proposal should be either None, "+\
106
+ f"{cuqi.distribution.Distribution.__class__.__name__} "+\
107
+ "conditioned only on 'location' and 'scale', lambda function, "+\
108
+ f"or {cuqi.distribution.Normal.__class__.__name__} conditioned "+\
109
+ "only on 'mean' and 'std'"
110
+
111
+ if value is None:
112
+ self._proposal = cuqi.distribution.Normal(
113
+ mean=lambda location: location,
114
+ std=lambda scale: scale,
115
+ geometry=self.dim,
116
+ )
117
+
118
+ elif isinstance(value, cuqi.distribution.Distribution) and sorted(
119
+ value.get_conditioning_variables()
120
+ ) == ["location", "scale"]:
121
+ self._proposal = value
122
+
123
+ elif isinstance(value, cuqi.distribution.Normal) and sorted(
124
+ value.get_conditioning_variables()
125
+ ) == ["mean", "std"]:
126
+ self._proposal = value(
127
+ mean=lambda location: location, std=lambda scale: scale
128
+ )
129
+
130
+ elif not isinstance(value, cuqi.distribution.Distribution) and callable(
131
+ value):
132
+ self._proposal = value
133
+
134
+ else:
135
+ raise ValueError(fail_msg)
136
+
137
+ def step(self):
138
+ # Initialize x_t which is used to store the current CWMH sample
139
+ x_t = self.current_point.copy()
140
+
141
+ # Initialize x_star which is used to store the proposed sample by
142
+ # updating the current sample component-by-component
143
+ x_star = self.current_point.copy()
144
+
145
+ # Propose a sample x_all_components from the proposal distribution
146
+ # for all the components
147
+ target_eval_t = self.current_target
148
+ if isinstance(self.proposal,cuqi.distribution.Distribution):
149
+ x_all_components = self.proposal(
150
+ location= self.current_point, scale=self.scale).sample()
151
+ else:
152
+ x_all_components = self.proposal(self.current_point, self.scale)
153
+
154
+ # Initialize acceptance rate
155
+ acc = np.zeros(self.dim)
156
+
157
+ # Loop over all the components of the sample and accept/reject
158
+ # each component update.
159
+ for j in range(self.dim):
160
+ # propose state x_star by updating the j-th component
161
+ x_star[j] = x_all_components[j]
162
+
163
+ # evaluate target
164
+ target_eval_star = self.target.logd(x_star)
165
+
166
+ # compute Metropolis acceptance ratio
167
+ alpha = min(0, target_eval_star - target_eval_t)
168
+
169
+ # accept/reject
170
+ u_theta = np.log(np.random.rand())
171
+ if (u_theta <= alpha): # accept
172
+ x_t[j] = x_all_components[j]
173
+ target_eval_t = target_eval_star
174
+ acc[j] = 1
175
+
176
+ x_star = x_t.copy()
177
+
178
+ self.current_target = target_eval_t
179
+ self.current_point = x_t
180
+
181
+ return acc
182
+
183
+ def tune(self, skip_len, update_count):
184
+ # Store update_count in variable i for readability
185
+ i = update_count
186
+
187
+ # Optimal acceptance rate for CWMH
188
+ star_acc = 0.21/self.dim + 0.23
189
+
190
+ # Mean of acceptance rate over the last skip_len samples
191
+ hat_acc = np.mean(self._acc[i*skip_len:(i+1)*skip_len], axis=0)
192
+
193
+ # Compute new intermediate scaling parameter scale_temp
194
+ # Factor zeta ensures that the variation of the scale update vanishes
195
+ zeta = 1/np.sqrt(update_count+1)
196
+ scale_temp = np.exp(
197
+ np.log(self._scale_temp) + zeta*(hat_acc-star_acc))
198
+
199
+ # Update the scale parameter
200
+ self.scale = np.minimum(scale_temp, np.ones(self.dim))
201
+ self._scale_temp = scale_temp
202
+
203
+ def get_state(self):
204
+ current_point = self.current_point
205
+ if isinstance(current_point, CUQIarray):
206
+ current_point = current_point.to_numpy()
207
+
208
+ return {'sampler_type': 'CWMH',
209
+ 'current_point': current_point,
210
+ 'current_target': self.current_target,
211
+ 'scale': self.scale}
212
+
213
+ def set_state(self, state):
214
+ current_point = state['current_point']
215
+ if not isinstance(current_point, CUQIarray):
216
+ current_point = CUQIarray(current_point,
217
+ geometry=self.target.geometry)
218
+
219
+ self.current_point = current_point
220
+ self.current_target = state['current_target']
221
+ self.scale = state['scale']
cuqi/samples/_samples.py CHANGED
@@ -8,13 +8,20 @@ from copy import copy
8
8
  from numbers import Number
9
9
 
10
10
  try:
11
- import arviz # Plotting tool
12
- except ImportError:
11
+ import arviz # Plotting tool
12
+ except ImportError as e:
13
13
  arviz = None
14
+ arviz_import_error = e
15
+
14
16
 
15
17
  def _check_for_arviz():
16
18
  if arviz is None:
17
- raise ImportError("The arviz package is required for this functionality. Please install arviz using `pip install arviz`.")
19
+ msg = "The arviz package is required for this functionality. "\
20
+ + "Please make sure arviz is installed. "\
21
+ + "See below for the original error message:\n"\
22
+ + arviz_import_error.args[0]
23
+
24
+ raise ImportError(msg)
18
25
 
19
26
 
20
27
  class Samples(object):