CUQIpy 1.4.0.post0.dev13__py3-none-any.whl → 1.4.0.post0.dev45__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.

Files changed (48) hide show
  1. cuqi/__init__.py +1 -0
  2. cuqi/_version.py +3 -3
  3. cuqi/experimental/__init__.py +1 -2
  4. cuqi/experimental/_recommender.py +4 -4
  5. cuqi/legacy/__init__.py +2 -0
  6. cuqi/legacy/sampler/__init__.py +11 -0
  7. cuqi/legacy/sampler/_conjugate.py +55 -0
  8. cuqi/legacy/sampler/_conjugate_approx.py +52 -0
  9. cuqi/legacy/sampler/_cwmh.py +196 -0
  10. cuqi/legacy/sampler/_gibbs.py +231 -0
  11. cuqi/legacy/sampler/_hmc.py +335 -0
  12. cuqi/legacy/sampler/_langevin_algorithm.py +198 -0
  13. cuqi/legacy/sampler/_laplace_approximation.py +184 -0
  14. cuqi/legacy/sampler/_mh.py +190 -0
  15. cuqi/legacy/sampler/_pcn.py +244 -0
  16. cuqi/legacy/sampler/_rto.py +284 -0
  17. cuqi/legacy/sampler/_sampler.py +182 -0
  18. cuqi/problem/_problem.py +87 -80
  19. cuqi/sampler/__init__.py +120 -8
  20. cuqi/sampler/_conjugate.py +376 -35
  21. cuqi/sampler/_conjugate_approx.py +40 -16
  22. cuqi/sampler/_cwmh.py +132 -138
  23. cuqi/{experimental/mcmc → sampler}/_direct.py +1 -1
  24. cuqi/sampler/_gibbs.py +269 -130
  25. cuqi/sampler/_hmc.py +328 -201
  26. cuqi/sampler/_langevin_algorithm.py +282 -98
  27. cuqi/sampler/_laplace_approximation.py +87 -117
  28. cuqi/sampler/_mh.py +47 -157
  29. cuqi/sampler/_pcn.py +56 -211
  30. cuqi/sampler/_rto.py +206 -140
  31. cuqi/sampler/_sampler.py +540 -135
  32. {cuqipy-1.4.0.post0.dev13.dist-info → cuqipy-1.4.0.post0.dev45.dist-info}/METADATA +1 -1
  33. {cuqipy-1.4.0.post0.dev13.dist-info → cuqipy-1.4.0.post0.dev45.dist-info}/RECORD +36 -35
  34. cuqi/experimental/mcmc/__init__.py +0 -122
  35. cuqi/experimental/mcmc/_conjugate.py +0 -396
  36. cuqi/experimental/mcmc/_conjugate_approx.py +0 -76
  37. cuqi/experimental/mcmc/_cwmh.py +0 -190
  38. cuqi/experimental/mcmc/_gibbs.py +0 -366
  39. cuqi/experimental/mcmc/_hmc.py +0 -462
  40. cuqi/experimental/mcmc/_langevin_algorithm.py +0 -382
  41. cuqi/experimental/mcmc/_laplace_approximation.py +0 -154
  42. cuqi/experimental/mcmc/_mh.py +0 -80
  43. cuqi/experimental/mcmc/_pcn.py +0 -89
  44. cuqi/experimental/mcmc/_rto.py +0 -350
  45. cuqi/experimental/mcmc/_sampler.py +0 -582
  46. {cuqipy-1.4.0.post0.dev13.dist-info → cuqipy-1.4.0.post0.dev45.dist-info}/WHEEL +0 -0
  47. {cuqipy-1.4.0.post0.dev13.dist-info → cuqipy-1.4.0.post0.dev45.dist-info}/licenses/LICENSE +0 -0
  48. {cuqipy-1.4.0.post0.dev13.dist-info → cuqipy-1.4.0.post0.dev45.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,9 @@
1
1
  import scipy as sp
2
2
  import numpy as np
3
3
  import cuqi
4
- from cuqi.distribution import Normal
5
4
  from cuqi.solver import CGLS
6
5
  from cuqi.sampler import Sampler
7
6
 
8
-
9
7
  class UGLA(Sampler):
10
8
  """ Unadjusted (Gaussian) Laplace Approximation sampler
11
9
 
@@ -16,103 +14,70 @@ class UGLA(Sampler):
16
14
 
17
15
  The inner solver is Conjugate Gradient Least Squares (CGLS) solver.
18
16
 
19
- For more details see: Uribe, Felipe, et al. "A hybrid Gibbs sampler for edge-preserving
20
- tomographic reconstruction with uncertain view angles." arXiv preprint arXiv:2104.06919 (2021).
17
+ For more details see: Uribe, Felipe, et al. A hybrid Gibbs sampler for edge-preserving
18
+ tomographic reconstruction with uncertain view angles. SIAM/ASA Journal on UQ,
19
+ https://doi.org/10.1137/21M1412268 (2022).
21
20
 
22
21
  Parameters
23
22
  ----------
24
23
  target : `cuqi.distribution.Posterior`
25
24
  The target posterior distribution to sample.
26
25
 
27
- x0 : ndarray
28
- Initial parameters. *Optional*
26
+ initial_point : ndarray, *Optional*
27
+ Initial parameters.
28
+ If not provided, it defaults to zeros.
29
29
 
30
30
  maxit : int
31
31
  Maximum number of inner iterations for solver when generating one sample.
32
+ If not provided, it defaults to 50.
32
33
 
33
34
  tol : float
34
- Tolerance for inner solver. Will stop before maxit if the inner solvers convergence check reaches tol.
35
+ Tolerance for inner solver.
36
+ The inner solvers will stop before maxit if convergence check reaches tol.
37
+ If not provided, it defaults to 1e-4.
35
38
 
36
39
  beta : float
37
- Smoothing parameter for the Gaussian approximation of the Laplace distribution. Larger beta is easier to sample but is a worse approximation.
38
-
39
- rng : np.random.RandomState
40
- Random number generator used for sampling. *Optional*
41
-
42
- callback : callable, *Optional*
43
- If set this function will be called after every sample.
44
- The signature of the callback function is `callback(sample, sample_index)`,
45
- where `sample` is the current sample and `sample_index` is the index of the sample.
46
- An example is shown in demos/demo31_callback.py.
47
-
48
- Returns
49
- -------
50
- cuqi.samples.Samples
51
- Samples from the posterior distribution.
52
-
40
+ Smoothing parameter for the Gaussian approximation of the Laplace distribution.
41
+ A small value in the range of 1e-7 to 1e-3 is recommended, though values out of this
42
+ range might give better results in some cases. Generally, a larger beta value makes
43
+ sampling easier but results in a worse approximation. See details in Section 3.3 of the paper.
44
+ If not provided, it defaults to 1e-5.
45
+
46
+ callback : callable, optional
47
+ A function that will be called after each sampling step. It can be useful for monitoring the sampler during sampling.
48
+ The function should take three arguments: the sampler object, the index of the current sampling step, the total number of requested samples. The last two arguments are integers. An example of the callback function signature is: `callback(sampler, sample_index, num_of_samples)`.
53
49
  """
50
+ def __init__(self, target=None, initial_point=None, maxit=50, tol=1e-4, beta=1e-5, **kwargs):
54
51
 
55
- def __init__(self, target, x0=None, maxit=50, tol=1e-4, beta=1e-5, rng=None, **kwargs):
56
-
57
- super().__init__(target, x0=x0, **kwargs)
58
-
59
- # Check target type
60
- if not isinstance(self.target, cuqi.distribution.Posterior):
61
- raise ValueError(f"To initialize an object of type {self.__class__}, 'target' need to be of type 'cuqi.distribution.Posterior'.")
52
+ super().__init__(target=target, initial_point=initial_point, **kwargs)
62
53
 
63
- # Check Affine model
64
- if not isinstance(self.target.likelihood.model, cuqi.model.AffineModel):
65
- raise TypeError("Model needs to be affine or linear")
66
-
67
- # Check Gaussian likelihood
68
- if not hasattr(self.target.likelihood.distribution, "sqrtprec"):
69
- raise TypeError("Distribution in Likelihood must contain a sqrtprec attribute")
70
-
71
- # Check that prior is LMRF
72
- if not isinstance(self.target.prior, cuqi.distribution.LMRF):
73
- raise ValueError('Unadjusted Gaussian Laplace approximation (UGLA) requires LMRF prior')
74
-
75
- # Modify initial guess since Sampler sets it to ones.
76
- if x0 is not None:
77
- self.x0 = x0
78
- else:
79
- self.x0 = np.zeros(self.target.prior.dim)
80
-
81
- # Store internal parameters
54
+ # Parameters
82
55
  self.maxit = maxit
83
56
  self.tol = tol
84
57
  self.beta = beta
85
- self.rng = rng
86
-
87
- def _sample_adapt(self, Ns, Nb):
88
- return self._sample(Ns, Nb)
89
-
90
- def _sample(self, Ns, Nb):
91
- """ Sample from the approximate posterior.
92
-
93
- Parameters
94
- ----------
95
- Ns : int
96
- Number of samples to draw.
97
-
98
- Nb : int
99
- Number of burn-in samples to discard.
58
+
59
+ def _initialize(self):
60
+ self._precompute()
100
61
 
101
- Returns
102
- -------
103
- samples : ndarray
104
- Samples from the approximate posterior.
62
+ @property
63
+ def prior(self):
64
+ return self.target.prior
105
65
 
106
- target_eval : ndarray
107
- Log-likelihood of each sample.
66
+ @property
67
+ def likelihood(self):
68
+ return self.target.likelihood
108
69
 
109
- acc : ndarray
110
- Acceptance rate of each sample.
70
+ @property
71
+ def model(self):
72
+ return self.target.model
73
+
74
+ @property
75
+ def _data(self):
76
+ return self.target.data - self.target.model._shift
111
77
 
112
- """
78
+ def _precompute(self):
113
79
 
114
- # Extract diff_op from target prior
115
- D = self.target.prior._diff_op
80
+ D = self.prior._diff_op
116
81
  n = D.shape[0]
117
82
 
118
83
  # Gaussian approximation of LMRF prior as function of x_k
@@ -120,65 +85,70 @@ class UGLA(Sampler):
120
85
  dd = 1/np.sqrt((D @ x_k)**2 + self.beta*np.ones(n))
121
86
  W = sp.sparse.diags(dd)
122
87
  return W.sqrt() @ D
88
+ self.Lk_fun = Lk_fun
123
89
 
124
- # Now prepare "LinearRTO" type sampler. TODO: Use LinearRTO for this instead
125
- self._shift = 0
126
-
127
- # Pre-computations
128
- self._model = self.target.likelihood.model
129
- self._data = self.target.likelihood.data - self.target.model._shift
130
90
  self._m = len(self._data)
131
- self._L1 = self.target.likelihood.distribution.sqrtprec
91
+ self._L1 = self.likelihood.distribution.sqrtprec
132
92
 
133
93
  # If prior location is scalar, repeat it to match dimensions
134
- if len(self.target.prior.location) == 1:
135
- self._priorloc = np.repeat(self.target.prior.location, self.dim)
94
+ if len(self.prior.location) == 1:
95
+ self._priorloc = np.repeat(self.prior.location, self.dim)
136
96
  else:
137
- self._priorloc = self.target.prior.location
97
+ self._priorloc = self.prior.location
138
98
 
139
99
  # Initial Laplace approx
140
- self._L2 = Lk_fun(self.x0)
100
+ self._L2 = Lk_fun(self.initial_point)
141
101
  self._L2mu = self._L2@self._priorloc
142
102
  self._b_tild = np.hstack([self._L1@self._data, self._L2mu])
143
103
 
144
- #self.n = len(self.x0)
145
-
146
104
  # Least squares form
147
105
  def M(x, flag):
148
106
  if flag == 1:
149
- out1 = self._L1 @ self._model._forward_func_no_shift(x) # Use forward function which excludes shift
150
- out2 = np.sqrt(1/self.target.prior.scale)*(self._L2 @ x)
107
+ out1 = self._L1 @ self.model._forward_func_no_shift(x) # Use forward function which excludes shift
108
+ out2 = np.sqrt(1/self.prior.scale)*(self._L2 @ x)
151
109
  out = np.hstack([out1, out2])
152
110
  elif flag == 2:
153
111
  idx = int(self._m)
154
- out1 = self._model._adjoint_func_no_shift(self._L1.T@x[:idx])
155
- out2 = np.sqrt(1/self.target.prior.scale)*(self._L2.T @ x[idx:])
112
+ out1 = self.model._adjoint_func_no_shift(self._L1.T@x[:idx])
113
+ out2 = np.sqrt(1/self.prior.scale)*(self._L2.T @ x[idx:])
156
114
  out = out1 + out2
157
- return out
158
-
159
- # Initialize samples
160
- N = Ns+Nb # number of simulations
161
- samples = np.empty((self.target.dim, N))
162
-
163
- # initial state
164
- samples[:, 0] = self.x0
165
- for s in range(N-1):
166
-
167
- # Update Laplace approximation
168
- self._L2 = Lk_fun(samples[:, s])
169
- self._L2mu = self._L2@self._priorloc
170
- self._b_tild = np.hstack([self._L1@self._data, self._L2mu])
171
-
172
- # Sample from approximate posterior
173
- e = Normal(mean=np.zeros(len(self._b_tild)), std=1).sample(rng=self.rng)
174
- y = self._b_tild + e # Perturb data
175
- sim = CGLS(M, y, samples[:, s], self.maxit, self.tol, self._shift)
176
- samples[:, s+1], _ = sim.solve()
115
+ return out
116
+ self.M = M
177
117
 
178
- self._print_progress(s+2,N) #s+2 is the sample number, s+1 is index assuming x0 is the first sample
179
- self._call_callback(samples[:, s+1], s+1)
118
+ def step(self):
119
+ # Update Laplace approximation
120
+ self._L2 = self.Lk_fun(self.current_point)
121
+ self._L2mu = self._L2@self._priorloc
122
+ self._b_tild = np.hstack([self._L1@self._data, self._L2mu])
123
+
124
+ # Sample from approximate posterior
125
+ e = np.random.randn(len(self._b_tild))
126
+ y = self._b_tild + e # Perturb data
127
+ sim = CGLS(self.M, y, self.current_point, self.maxit, self.tol)
128
+ self.current_point, _ = sim.solve()
129
+ acc = 1
130
+ return acc
131
+
132
+ def tune(self, skip_len, update_count):
133
+ pass
134
+
135
+ def validate_target(self):
136
+ # Check target type
137
+ if not isinstance(self.target, cuqi.distribution.Posterior):
138
+ raise ValueError(f"To initialize an object of type {self.__class__}, 'target' need to be of type 'cuqi.distribution.Posterior'.")
139
+
140
+ # Check Affine model
141
+ if not isinstance(self.likelihood.model, cuqi.model.AffineModel):
142
+ raise TypeError("Model needs to be affine or linear")
180
143
 
181
- # remove burn-in
182
- samples = samples[:, Nb:]
144
+ # Check Gaussian likelihood
145
+ if not hasattr(self.likelihood.distribution, "sqrtprec"):
146
+ raise TypeError("Distribution in Likelihood must contain a sqrtprec attribute")
147
+
148
+ # Check that prior is LMRF
149
+ if not isinstance(self.prior, cuqi.distribution.LMRF):
150
+ raise ValueError('Unadjusted Gaussian Laplace approximation (UGLA) requires LMRF prior')
183
151
 
184
- return samples, None, None
152
+ def _get_default_initial_point(self, dim):
153
+ """ Get the default initial point for the sampler. Defaults to an array of zeros. """
154
+ return np.zeros(dim)
cuqi/sampler/_mh.py CHANGED
@@ -4,187 +4,77 @@ from cuqi.sampler import ProposalBasedSampler
4
4
 
5
5
 
6
6
  class MH(ProposalBasedSampler):
7
- """Metropolis Hastings sampler.
8
-
9
- Allows sampling of a target distribution by random-walk sampling of a proposal distribution along with an accept/reject step.
7
+ """ Metropolis-Hastings (MH) sampler.
10
8
 
11
9
  Parameters
12
10
  ----------
11
+ target : cuqi.density.Density
12
+ Target density or distribution.
13
13
 
14
- target : `cuqi.distribution.Distribution` or lambda function
15
- The target distribution to sample. Custom logpdfs are supported by using a :class:`cuqi.distribution.UserDefinedDistribution`.
16
-
17
- proposal : `cuqi.distribution.Distribution` or callable method
18
- The proposal to sample from. If a callable method it should provide a single independent sample from proposal distribution. Defaults to a Gaussian proposal. *Optional*.
14
+ proposal : cuqi.distribution.Distribution or callable
15
+ Proposal distribution. If None, a random walk MH is used (i.e., Gaussian proposal with identity covariance).
19
16
 
20
17
  scale : float
21
- Scale parameter used to define correlation between previous and proposed sample in random-walk. *Optional*.
22
-
23
- x0 : ndarray
24
- Initial parameters. *Optional*
18
+ Scaling parameter for the proposal distribution.
25
19
 
26
- dim : int
27
- Dimension of parameter space. Required if target and proposal are callable functions. *Optional*.
20
+ kwargs : dict
21
+ Additional keyword arguments to be passed to the base class :class:`ProposalBasedSampler`.
28
22
 
29
- callback : callable, *Optional*
30
- If set this function will be called after every sample.
31
- The signature of the callback function is `callback(sample, sample_index)`,
32
- where `sample` is the current sample and `sample_index` is the index of the sample.
33
- An example is shown in demos/demo31_callback.py.
34
-
35
- Example
36
- -------
37
- .. code-block:: python
23
+ """
38
24
 
39
- # Parameters
40
- dim = 5 # Dimension of distribution
41
- mu = np.arange(dim) # Mean of Gaussian
42
- std = 1 # standard deviation of Gaussian
25
+ _STATE_KEYS = ProposalBasedSampler._STATE_KEYS.union({'scale', '_scale_temp'})
43
26
 
44
- # Logpdf function
45
- logpdf_func = lambda x: -1/(std**2)*np.sum((x-mu)**2)
27
+ def __init__(self, target=None, proposal=None, scale=1, **kwargs):
28
+ super().__init__(target, proposal=proposal, scale=scale, **kwargs)
46
29
 
47
- # Define distribution from logpdf as UserDefinedDistribution (sample and gradients also supported)
48
- target = cuqi.distribution.UserDefinedDistribution(dim=dim, logpdf_func=logpdf_func)
30
+ def _initialize(self):
31
+ # Due to a bug? in old MH, we must keep track of this extra variable to match behavior.
32
+ self._scale_temp = self.scale
49
33
 
50
- # Set up sampler
51
- sampler = cuqi.sampler.MH(target, scale=1)
34
+ def validate_target(self):
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._get_default_initial_point(self.dim))):
37
+ raise ValueError("Target does not have valid logd")
52
38
 
53
- # Sample
54
- samples = sampler.sample(2000)
39
+ def validate_proposal(self):
40
+ if not isinstance(self.proposal, cuqi.distribution.Distribution):
41
+ raise ValueError("Proposal must be a cuqi.distribution.Distribution object")
42
+ if not self.proposal.is_symmetric:
43
+ raise ValueError("Proposal must be symmetric")
55
44
 
56
- """
57
- #target, proposal=None, scale=1, x0=None, dim=None
58
- # super().__init__(target, proposal=proposal, scale=scale, x0=x0, dim=dim)
59
- def __init__(self, target, proposal=None, scale=None, x0=None, dim=None, **kwargs):
60
- """ Metropolis-Hastings (MH) sampler. Default (if proposal is None) is random walk MH with proposal that is Gaussian with identity covariance"""
61
- super().__init__(target, proposal=proposal, scale=scale, x0=x0, dim=dim, **kwargs)
62
-
63
-
64
- @ProposalBasedSampler.proposal.setter
65
- def proposal(self, value):
66
- fail_msg = "Proposal should be either None, symmetric cuqi.distribution.Distribution or a lambda function."
67
-
68
- if value is None:
69
- self._proposal = cuqi.distribution.Gaussian(np.zeros(self.dim), 1)
70
- elif not isinstance(value, cuqi.distribution.Distribution) and callable(value):
71
- raise NotImplementedError(fail_msg)
72
- elif isinstance(value, cuqi.distribution.Distribution) and value.is_symmetric:
73
- self._proposal = value
74
- else:
75
- raise ValueError(fail_msg)
76
- self._proposal.geometry = self.target.geometry
77
-
78
- def _sample(self, N, Nb):
79
- if self.scale is None:
80
- raise ValueError("Scale must be set to sample without adaptation. Consider using sample_adapt instead.")
81
-
82
- Ns = N+Nb # number of simulations
83
-
84
- # allocation
85
- samples = np.empty((self.dim, Ns))
86
- target_eval = np.empty(Ns)
87
- acc = np.zeros(Ns, dtype=int)
88
-
89
- # initial state
90
- samples[:, 0] = self.x0
91
- target_eval[0] = self.target.logd(self.x0)
92
- acc[0] = 1
93
-
94
- # run MCMC
95
- for s in range(Ns-1):
96
- # run component by component
97
- samples[:, s+1], target_eval[s+1], acc[s+1] = self.single_update(samples[:, s], target_eval[s])
98
- self._print_progress(s+2,Ns) #s+2 is the sample number, s+1 is index assuming x0 is the first sample
99
- self._call_callback(samples[:, s+1], s+1)
100
-
101
- # remove burn-in
102
- samples = samples[:, Nb:]
103
- target_eval = target_eval[Nb:]
104
- accave = acc[Nb:].mean()
105
- print('\nAverage acceptance rate:', accave, '\n')
106
- #
107
- return samples, target_eval, accave
108
-
109
- def _sample_adapt(self, N, Nb):
110
- # Set intial scale if not set
111
- if self.scale is None:
112
- self.scale = 0.1
113
-
114
- Ns = N+Nb # number of simulations
115
-
116
- # allocation
117
- samples = np.empty((self.dim, Ns))
118
- target_eval = np.empty(Ns)
119
- acc = np.zeros(Ns)
120
-
121
- # initial state
122
- samples[:, 0] = self.x0
123
- target_eval[0] = self.target.logd(self.x0)
124
- acc[0] = 1
125
-
126
- # initial adaptation params
127
- Na = int(0.1*N) # iterations to adapt
128
- hat_acc = np.empty(int(np.floor(Ns/Na))) # average acceptance rate of the chains
129
- lambd = self.scale
130
- star_acc = 0.234 # target acceptance rate RW
131
- i, idx = 0, 0
132
-
133
- # run MCMC
134
- for s in range(Ns-1):
135
- # run component by component
136
- samples[:, s+1], target_eval[s+1], acc[s+1] = self.single_update(samples[:, s], target_eval[s])
137
-
138
- # adapt prop spread using acc of past samples
139
- if ((s+1) % Na == 0):
140
- # evaluate average acceptance rate
141
- hat_acc[i] = np.mean(acc[idx:idx+Na])
142
-
143
- # d. compute new scaling parameter
144
- zeta = 1/np.sqrt(i+1) # ensures that the variation of lambda(i) vanishes
145
- lambd = np.exp(np.log(lambd) + zeta*(hat_acc[i]-star_acc))
146
-
147
- # update parameters
148
- self.scale = min(lambd, 1)
149
-
150
- # update counters
151
- i += 1
152
- idx += Na
153
-
154
- # display iterations
155
- self._print_progress(s+2,Ns) #s+2 is the sample number, s+1 is index assuming x0 is the first sample
156
-
157
-
158
- # remove burn-in
159
- samples = samples[:, Nb:]
160
- target_eval = target_eval[Nb:]
161
- accave = acc[Nb:].mean()
162
- print('\nAverage acceptance rate:', accave, 'MCMC scale:', self.scale, '\n')
163
-
164
- return samples, target_eval, accave
165
-
166
-
167
- def single_update(self, x_t, target_eval_t):
45
+ def step(self):
168
46
  # propose state
169
47
  xi = self.proposal.sample(1) # sample from the proposal
170
- x_star = x_t + self.scale*xi.flatten() # MH proposal
48
+ x_star = self.current_point + self.scale*xi.flatten() # MH proposal
171
49
 
172
50
  # evaluate target
173
51
  target_eval_star = self.target.logd(x_star)
174
52
 
175
53
  # ratio and acceptance probability
176
- ratio = target_eval_star - target_eval_t # proposal is symmetric
54
+ ratio = target_eval_star - self.current_target_logd # proposal is symmetric
177
55
  alpha = min(0, ratio)
178
56
 
179
57
  # accept/reject
180
58
  u_theta = np.log(np.random.rand())
181
- if (u_theta <= alpha):
182
- x_next = x_star
183
- target_eval_next = target_eval_star
59
+ acc = 0
60
+ if (u_theta <= alpha) and \
61
+ (not np.isnan(target_eval_star)) and \
62
+ (not np.isinf(target_eval_star)):
63
+ self.current_point = x_star
64
+ self.current_target_logd = target_eval_star
184
65
  acc = 1
185
- else:
186
- x_next = x_t
187
- target_eval_next = target_eval_t
188
- acc = 0
189
66
 
190
- return x_next, target_eval_next, acc
67
+ return acc
68
+
69
+ def tune(self, skip_len, update_count):
70
+ hat_acc = np.mean(self._acc[-skip_len:])
71
+
72
+ # d. compute new scaling parameter
73
+ zeta = 1/np.sqrt(update_count+1) # ensures that the variation of lambda(i) vanishes
74
+
75
+ # We use self._scale_temp here instead of self.scale in update. This might be a bug,
76
+ # but is equivalent to old MH
77
+ self._scale_temp = np.exp(np.log(self._scale_temp) + zeta*(hat_acc-0.234))
78
+
79
+ # update parameters
80
+ self.scale = min(self._scale_temp, 1)