CUQIpy 1.3.0__py3-none-any.whl → 1.4.0.post0.dev61__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.
Files changed (72) hide show
  1. cuqi/__init__.py +1 -0
  2. cuqi/_version.py +3 -3
  3. cuqi/density/_density.py +9 -1
  4. cuqi/distribution/__init__.py +1 -1
  5. cuqi/distribution/_beta.py +1 -1
  6. cuqi/distribution/_cauchy.py +2 -2
  7. cuqi/distribution/_distribution.py +24 -15
  8. cuqi/distribution/_joint_distribution.py +97 -12
  9. cuqi/distribution/_posterior.py +9 -0
  10. cuqi/distribution/_truncated_normal.py +3 -3
  11. cuqi/distribution/_uniform.py +36 -2
  12. cuqi/experimental/__init__.py +1 -1
  13. cuqi/experimental/_recommender.py +216 -0
  14. cuqi/experimental/geometry/_productgeometry.py +3 -3
  15. cuqi/geometry/_geometry.py +12 -1
  16. cuqi/implicitprior/__init__.py +1 -1
  17. cuqi/implicitprior/_regularizedGaussian.py +40 -4
  18. cuqi/implicitprior/_restorator.py +35 -1
  19. cuqi/legacy/__init__.py +2 -0
  20. cuqi/legacy/sampler/__init__.py +11 -0
  21. cuqi/legacy/sampler/_conjugate.py +55 -0
  22. cuqi/legacy/sampler/_conjugate_approx.py +52 -0
  23. cuqi/legacy/sampler/_cwmh.py +196 -0
  24. cuqi/legacy/sampler/_gibbs.py +231 -0
  25. cuqi/legacy/sampler/_hmc.py +335 -0
  26. cuqi/legacy/sampler/_langevin_algorithm.py +198 -0
  27. cuqi/legacy/sampler/_laplace_approximation.py +184 -0
  28. cuqi/legacy/sampler/_mh.py +190 -0
  29. cuqi/legacy/sampler/_pcn.py +244 -0
  30. cuqi/{experimental/mcmc → legacy/sampler}/_rto.py +134 -152
  31. cuqi/legacy/sampler/_sampler.py +182 -0
  32. cuqi/likelihood/_likelihood.py +1 -1
  33. cuqi/model/_model.py +1248 -357
  34. cuqi/pde/__init__.py +4 -0
  35. cuqi/pde/_observation_map.py +36 -0
  36. cuqi/pde/_pde.py +133 -32
  37. cuqi/problem/_problem.py +88 -82
  38. cuqi/sampler/__init__.py +120 -8
  39. cuqi/sampler/_conjugate.py +376 -35
  40. cuqi/sampler/_conjugate_approx.py +40 -16
  41. cuqi/sampler/_cwmh.py +132 -138
  42. cuqi/{experimental/mcmc → sampler}/_direct.py +1 -1
  43. cuqi/sampler/_gibbs.py +269 -130
  44. cuqi/sampler/_hmc.py +328 -201
  45. cuqi/sampler/_langevin_algorithm.py +282 -98
  46. cuqi/sampler/_laplace_approximation.py +87 -117
  47. cuqi/sampler/_mh.py +47 -157
  48. cuqi/sampler/_pcn.py +56 -211
  49. cuqi/sampler/_rto.py +206 -140
  50. cuqi/sampler/_sampler.py +540 -135
  51. cuqi/solver/_solver.py +6 -2
  52. cuqi/testproblem/_testproblem.py +2 -3
  53. cuqi/utilities/__init__.py +3 -1
  54. cuqi/utilities/_utilities.py +94 -12
  55. {CUQIpy-1.3.0.dist-info → cuqipy-1.4.0.post0.dev61.dist-info}/METADATA +6 -4
  56. cuqipy-1.4.0.post0.dev61.dist-info/RECORD +102 -0
  57. {CUQIpy-1.3.0.dist-info → cuqipy-1.4.0.post0.dev61.dist-info}/WHEEL +1 -1
  58. CUQIpy-1.3.0.dist-info/RECORD +0 -100
  59. cuqi/experimental/mcmc/__init__.py +0 -123
  60. cuqi/experimental/mcmc/_conjugate.py +0 -345
  61. cuqi/experimental/mcmc/_conjugate_approx.py +0 -76
  62. cuqi/experimental/mcmc/_cwmh.py +0 -193
  63. cuqi/experimental/mcmc/_gibbs.py +0 -318
  64. cuqi/experimental/mcmc/_hmc.py +0 -464
  65. cuqi/experimental/mcmc/_langevin_algorithm.py +0 -392
  66. cuqi/experimental/mcmc/_laplace_approximation.py +0 -156
  67. cuqi/experimental/mcmc/_mh.py +0 -80
  68. cuqi/experimental/mcmc/_pcn.py +0 -89
  69. cuqi/experimental/mcmc/_sampler.py +0 -566
  70. cuqi/experimental/mcmc/_utilities.py +0 -17
  71. {CUQIpy-1.3.0.dist-info → cuqipy-1.4.0.post0.dev61.dist-info/licenses}/LICENSE +0 -0
  72. {CUQIpy-1.3.0.dist-info → cuqipy-1.4.0.post0.dev61.dist-info}/top_level.txt +0 -0
@@ -1,318 +0,0 @@
1
- from cuqi.distribution import JointDistribution
2
- from cuqi.experimental.mcmc import Sampler
3
- from cuqi.samples import Samples, JointSamples
4
- from cuqi.experimental.mcmc import NUTS
5
- from typing import Dict
6
- import numpy as np
7
- import warnings
8
-
9
- try:
10
- from tqdm import tqdm
11
- except ImportError:
12
- def tqdm(iterable, **kwargs):
13
- warnings.warn("Module mcmc: tqdm not found. Install tqdm to get sampling progress.")
14
- return iterable
15
-
16
- # Not subclassed from Sampler as Gibbs handles multiple samplers and samples multiple parameters
17
- # Similar approach as for JointDistribution
18
- class HybridGibbs:
19
- """
20
- Hybrid Gibbs sampler for sampling a joint distribution.
21
-
22
- Gibbs sampling samples the variables of the distribution sequentially,
23
- one variable at a time. When a variable represents a random vector, the
24
- whole vector is sampled simultaneously.
25
-
26
- The sampling of each variable is done by sampling from the conditional
27
- distribution of that variable given the values of the other variables.
28
- This is often a very efficient way of sampling from a joint distribution
29
- if the conditional distributions are easy to sample from.
30
-
31
- Hybrid Gibbs sampler is a generalization of the Gibbs sampler where the
32
- conditional distributions are sampled using different MCMC samplers.
33
-
34
- When the conditionals are sampled exactly, the samples from the Gibbs
35
- sampler converge to the joint distribution. See e.g.
36
- Gelman et al. "Bayesian Data Analysis" (2014), Third Edition
37
- for more details.
38
-
39
- In each Gibbs step, the corresponding sampler has the initial_point
40
- and initial_scale (if applicable) set to the value of the previous step
41
- and the sampler is reinitialized. This means that the sampling is not
42
- fully stateful at this point. This means samplers like NUTS will lose
43
- their internal state between Gibbs steps.
44
-
45
- Parameters
46
- ----------
47
- target : cuqi.distribution.JointDistribution
48
- Target distribution to sample from.
49
-
50
- sampling_strategy : dict
51
- Dictionary of sampling strategies for each variable.
52
- Keys are variable names.
53
- Values are sampler objects.
54
-
55
- num_sampling_steps : dict, *optional*
56
- Dictionary of number of sampling steps for each variable.
57
- The sampling steps are defined as the number of times the sampler
58
- will call its step method in each Gibbs step.
59
- Default is 1 for all variables.
60
-
61
- Example
62
- -------
63
- .. code-block:: python
64
-
65
- import cuqi
66
- import numpy as np
67
-
68
- # Model and data
69
- A, y_obs, probinfo = cuqi.testproblem.Deconvolution1D(phantom='sinc').get_components()
70
- n = A.domain_dim
71
-
72
- # Define distributions
73
- d = cuqi.distribution.Gamma(1, 1e-4)
74
- l = cuqi.distribution.Gamma(1, 1e-4)
75
- x = cuqi.distribution.GMRF(np.zeros(n), lambda d: d)
76
- y = cuqi.distribution.Gaussian(A, lambda l: 1/l)
77
-
78
- # Combine into a joint distribution and create posterior
79
- joint = cuqi.distribution.JointDistribution(d, l, x, y)
80
- posterior = joint(y=y_obs)
81
-
82
- # Define sampling strategy
83
- sampling_strategy = {
84
- 'x': cuqi.experimental.mcmc.LinearRTO(maxit=15),
85
- 'd': cuqi.experimental.mcmc.Conjugate(),
86
- 'l': cuqi.experimental.mcmc.Conjugate(),
87
- }
88
-
89
- # Define Gibbs sampler
90
- sampler = cuqi.experimental.mcmc.HybridGibbs(posterior, sampling_strategy)
91
-
92
- # Run sampler
93
- sampler.warmup(200)
94
- sampler.sample(1000)
95
-
96
- # Get samples removing burn-in
97
- samples = sampler.get_samples().burnthin(200)
98
-
99
- # Plot results
100
- samples['x'].plot_ci(exact=probinfo.exactSolution)
101
- samples['d'].plot_trace(figsize=(8,2))
102
- samples['l'].plot_trace(figsize=(8,2))
103
-
104
- """
105
-
106
- def __init__(self, target: JointDistribution, sampling_strategy: Dict[str, Sampler], num_sampling_steps: Dict[str, int] = None):
107
-
108
- # Store target and allow conditioning to reduce to a single density
109
- self.target = target() # Create a copy of target distribution (to avoid modifying the original)
110
-
111
- # Store sampler instances (again as a copy to avoid modifying the original)
112
- self.samplers = sampling_strategy.copy()
113
-
114
- # Store number of sampling steps for each parameter
115
- self.num_sampling_steps = num_sampling_steps
116
-
117
- # Store parameter names
118
- self.par_names = self.target.get_parameter_names()
119
-
120
- # Initialize sampler (after target is set)
121
- self._initialize()
122
-
123
- def _initialize(self):
124
- """ Initialize sampler """
125
-
126
- # Initial points
127
- self.current_samples = self._get_initial_points()
128
-
129
- # Initialize sampling steps
130
- self._initialize_num_sampling_steps()
131
-
132
- # Allocate samples
133
- self._allocate_samples()
134
-
135
- # Set targets
136
- self._set_targets()
137
-
138
- # Initialize the samplers
139
- self._initialize_samplers()
140
-
141
- # Validate all targets for samplers.
142
- self.validate_targets()
143
-
144
- # ------------ Public methods ------------
145
- def validate_targets(self):
146
- """ Validate each of the conditional targets used in the Gibbs steps """
147
- if not isinstance(self.target, JointDistribution):
148
- raise ValueError('Target distribution must be a JointDistribution.')
149
- for sampler in self.samplers.values():
150
- sampler.validate_target()
151
-
152
- def sample(self, Ns) -> 'HybridGibbs':
153
- """ Sample from the joint distribution using Gibbs sampling
154
-
155
- Parameters
156
- ----------
157
- Ns : int
158
- The number of samples to draw.
159
-
160
- """
161
-
162
- for _ in tqdm(range(Ns), "Sample: "):
163
-
164
- self.step()
165
-
166
- self._store_samples()
167
-
168
- return self
169
-
170
- def warmup(self, Nb, tune_freq=0.1) -> 'HybridGibbs':
171
- """ Warmup (tune) the samplers in the Gibbs sampling scheme
172
-
173
- Parameters
174
- ----------
175
- Nb : int
176
- The number of samples to draw during warmup.
177
-
178
- tune_freq : float, optional
179
- Frequency of tuning the samplers. Tuning is performed every tune_freq*Nb steps.
180
-
181
- """
182
-
183
- tune_interval = max(int(tune_freq * Nb), 1)
184
-
185
- for idx in tqdm(range(Nb), "Warmup: "):
186
-
187
- self.step()
188
-
189
- # Tune the sampler at tuning intervals (matching behavior of Sampler class)
190
- if (idx + 1) % tune_interval == 0:
191
- self.tune(tune_interval, idx // tune_interval)
192
-
193
- self._store_samples()
194
-
195
- return self
196
-
197
- def get_samples(self) -> Dict[str, Samples]:
198
- samples_object = JointSamples()
199
- for par_name in self.par_names:
200
- samples_array = np.array(self.samples[par_name]).T
201
- samples_object[par_name] = Samples(samples_array, self.target.get_density(par_name).geometry)
202
- return samples_object
203
-
204
- def step(self):
205
- """ Sequentially go through all parameters and sample them conditionally on each other """
206
-
207
- # Sample from each conditional distribution
208
- for par_name in self.par_names:
209
-
210
- # Set target for current parameter
211
- self._set_target(par_name)
212
-
213
- # Get sampler
214
- sampler = self.samplers[par_name]
215
-
216
- # Instead of simply changing the target of the sampler, we reinitialize it.
217
- # This is to ensure that all internal variables are set to match the new target.
218
- # To return the sampler to the old state and history, we first extract the state and history
219
- # before reinitializing the sampler and then set the state and history back to the sampler
220
-
221
- # Extract state and history from sampler
222
- if isinstance(sampler, NUTS): # Special case for NUTS as it is not playing nice with get_state and get_history
223
- sampler.initial_point = sampler.current_point
224
- else:
225
- sampler_state = sampler.get_state()
226
- sampler_history = sampler.get_history()
227
-
228
- # Reinitialize sampler
229
- sampler.reinitialize()
230
-
231
- # Set state and history back to sampler
232
- if not isinstance(sampler, NUTS): # Again, special case for NUTS.
233
- sampler.set_state(sampler_state)
234
- sampler.set_history(sampler_history)
235
-
236
- # Allow for multiple sampling steps in each Gibbs step
237
- for _ in range(self.num_sampling_steps[par_name]):
238
- # Sampling step
239
- acc = sampler.step()
240
-
241
- # Store acceptance rate in sampler (matching behavior of Sampler class Sample method)
242
- sampler._acc.append(acc)
243
-
244
- # Extract samples (Ensure even 1-dimensional samples are 1D arrays)
245
- if isinstance(sampler.current_point, np.ndarray):
246
- self.current_samples[par_name] = sampler.current_point.reshape(-1)
247
- else:
248
- self.current_samples[par_name] = sampler.current_point
249
-
250
- def tune(self, skip_len, update_count):
251
- """ Run a single tuning step on each of the samplers in the Gibbs sampling scheme
252
-
253
- Parameters
254
- ----------
255
- skip_len : int
256
- Defines the number of steps in between tuning (i.e. the tuning interval).
257
-
258
- update_count : int
259
- The number of times tuning has been performed. Can be used for internal bookkeeping.
260
-
261
- """
262
- for par_name in self.par_names:
263
- self.samplers[par_name].tune(skip_len=skip_len, update_count=update_count)
264
-
265
- # ------------ Private methods ------------
266
- def _initialize_samplers(self):
267
- """ Initialize samplers """
268
- for sampler in self.samplers.values():
269
- if isinstance(sampler, NUTS):
270
- print(f'Warning: NUTS sampler is not fully stateful in HybridGibbs. Sampler will be reinitialized in each Gibbs step.')
271
- sampler.initialize()
272
-
273
- def _initialize_num_sampling_steps(self):
274
- """ Initialize the number of sampling steps for each sampler. Defaults to 1 if not set by user """
275
-
276
- if self.num_sampling_steps is None:
277
- self.num_sampling_steps = {par_name: 1 for par_name in self.par_names}
278
-
279
- for par_name in self.par_names:
280
- if par_name not in self.num_sampling_steps:
281
- self.num_sampling_steps[par_name] = 1
282
-
283
-
284
- def _set_targets(self):
285
- """ Set targets for all samplers using the current samples """
286
- par_names = self.par_names
287
- for par_name in par_names:
288
- self._set_target(par_name)
289
-
290
- def _set_target(self, par_name):
291
- """ Set target conditional distribution for a single parameter using the current samples """
292
- # Get all other conditional parameters other than the current parameter and update the target
293
- # This defines - from a joint p(x,y,z) - the conditional distribution p(x|y,z) or p(y|x,z) or p(z|x,y)
294
- conditional_params = {par_name_: self.current_samples[par_name_] for par_name_ in self.par_names if par_name_ != par_name}
295
- self.samplers[par_name].target = self.target(**conditional_params)
296
-
297
- def _allocate_samples(self):
298
- """ Allocate memory for samples """
299
- samples = {}
300
- for par_name in self.par_names:
301
- samples[par_name] = []
302
- self.samples = samples
303
-
304
- def _get_initial_points(self):
305
- """ Get initial points for each parameter """
306
- initial_points = {}
307
- for par_name in self.par_names:
308
- sampler = self.samplers[par_name]
309
- if sampler.initial_point is None:
310
- sampler.initial_point = sampler._get_default_initial_point(self.target.get_density(par_name).dim)
311
- initial_points[par_name] = sampler.initial_point
312
-
313
- return initial_points
314
-
315
- def _store_samples(self):
316
- """ Store current samples at index i of samples dict """
317
- for par_name in self.par_names:
318
- self.samples[par_name].append(self.current_samples[par_name])