CUQIpy 1.0.0.post0.dev91__py3-none-any.whl → 1.0.0.post0.dev145__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.
- {CUQIpy-1.0.0.post0.dev91.dist-info → CUQIpy-1.0.0.post0.dev145.dist-info}/METADATA +1 -1
- {CUQIpy-1.0.0.post0.dev91.dist-info → CUQIpy-1.0.0.post0.dev145.dist-info}/RECORD +13 -12
- cuqi/_version.py +3 -3
- cuqi/experimental/mcmc/__init__.py +1 -0
- cuqi/experimental/mcmc/_cwmh.py +2 -22
- cuqi/experimental/mcmc/_langevin_algorithm.py +12 -41
- cuqi/experimental/mcmc/_mh.py +4 -12
- cuqi/experimental/mcmc/_pcn.py +5 -15
- cuqi/experimental/mcmc/_rto.py +270 -0
- cuqi/experimental/mcmc/_sampler.py +109 -19
- {CUQIpy-1.0.0.post0.dev91.dist-info → CUQIpy-1.0.0.post0.dev145.dist-info}/LICENSE +0 -0
- {CUQIpy-1.0.0.post0.dev91.dist-info → CUQIpy-1.0.0.post0.dev145.dist-info}/WHEEL +0 -0
- {CUQIpy-1.0.0.post0.dev91.dist-info → CUQIpy-1.0.0.post0.dev145.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: CUQIpy
|
|
3
|
-
Version: 1.0.0.post0.
|
|
3
|
+
Version: 1.0.0.post0.dev145
|
|
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=
|
|
3
|
+
cuqi/_version.py,sha256=Qc2vgfclx0EbaHAQZJpzQEPFGq1LO7Yj1wMffmN8Llo,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
|
|
@@ -32,12 +32,13 @@ 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=
|
|
36
|
-
cuqi/experimental/mcmc/_cwmh.py,sha256=
|
|
37
|
-
cuqi/experimental/mcmc/_langevin_algorithm.py,sha256=
|
|
38
|
-
cuqi/experimental/mcmc/_mh.py,sha256=
|
|
39
|
-
cuqi/experimental/mcmc/_pcn.py,sha256=
|
|
40
|
-
cuqi/experimental/mcmc/
|
|
35
|
+
cuqi/experimental/mcmc/__init__.py,sha256=vVnohcm4EIUwbp1sr3LbB0BkXO8jyZsbiKMJmIgetYY,314
|
|
36
|
+
cuqi/experimental/mcmc/_cwmh.py,sha256=yRlTk5a1QYfH3JyCecfOOTeDf-4-tmJ3Tl2Bc3pyp1Y,7336
|
|
37
|
+
cuqi/experimental/mcmc/_langevin_algorithm.py,sha256=MX48u3GYgCckB6Q5h5kXr_qdIaLQH2toOG5u29OY7gk,8245
|
|
38
|
+
cuqi/experimental/mcmc/_mh.py,sha256=aIV1Ntq0EAq3QJ1_X-DbP7eDAL-d_Or7d3RUO-R48I4,3090
|
|
39
|
+
cuqi/experimental/mcmc/_pcn.py,sha256=3M8zhQGQa53Gz04AkC8wJM61_5rIjGVnhPefi8m4dbY,3531
|
|
40
|
+
cuqi/experimental/mcmc/_rto.py,sha256=jSPznr34XPfWM6LysWIiN4hE-vtyti3cHyvzy9ruykg,11349
|
|
41
|
+
cuqi/experimental/mcmc/_sampler.py,sha256=hbZwUHcEZFSSVd2tICcp9FdcK9UKB_-izdM7w4xwijs,14408
|
|
41
42
|
cuqi/geometry/__init__.py,sha256=Tz1WGzZBY-QGH3c0GiyKm9XHN8MGGcnU6TUHLZkzB3o,842
|
|
42
43
|
cuqi/geometry/_geometry.py,sha256=WYFC-4_VBTW73b2ldsnfGYKvdSiCE8plr89xTSmkadg,46804
|
|
43
44
|
cuqi/implicitprior/__init__.py,sha256=ZRZ9fgxgEl5n0A9F7WCl1_jid-GUiC8ZLkyTmGQmFlY,100
|
|
@@ -74,8 +75,8 @@ cuqi/testproblem/_testproblem.py,sha256=x769LwwRdJdzIiZkcQUGb_5-vynNTNALXWKato7s
|
|
|
74
75
|
cuqi/utilities/__init__.py,sha256=EfxHLdsyDNugbmbzs43nV_AeKcycM9sVBjG9WZydagA,351
|
|
75
76
|
cuqi/utilities/_get_python_variable_name.py,sha256=QwlBVj2koJRA8s8pWd554p7-ElcI7HUwY32HknaR92E,1827
|
|
76
77
|
cuqi/utilities/_utilities.py,sha256=At3DOXRdF3GwLkVcM2FXooGyjAGfPkIM0bRzhTfLmWk,8046
|
|
77
|
-
CUQIpy-1.0.0.post0.
|
|
78
|
-
CUQIpy-1.0.0.post0.
|
|
79
|
-
CUQIpy-1.0.0.post0.
|
|
80
|
-
CUQIpy-1.0.0.post0.
|
|
81
|
-
CUQIpy-1.0.0.post0.
|
|
78
|
+
CUQIpy-1.0.0.post0.dev145.dist-info/LICENSE,sha256=kJWRPrtRoQoZGXyyvu50Uc91X6_0XRaVfT0YZssicys,10799
|
|
79
|
+
CUQIpy-1.0.0.post0.dev145.dist-info/METADATA,sha256=REboBBeNZ3O4SrH9hNrNAYgvQPU9XP49ezl4bHyCigQ,18393
|
|
80
|
+
CUQIpy-1.0.0.post0.dev145.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
81
|
+
CUQIpy-1.0.0.post0.dev145.dist-info/top_level.txt,sha256=AgmgMc6TKfPPqbjV0kvAoCBN334i_Lwwojc7HE3ZwD0,5
|
|
82
|
+
CUQIpy-1.0.0.post0.dev145.dist-info/RECORD,,
|
cuqi/_version.py
CHANGED
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2024-04-
|
|
11
|
+
"date": "2024-04-15T12:29:37+0200",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "1.0.0.post0.
|
|
14
|
+
"full-revisionid": "744dccfaf90c7e50a11b7c5a4a59d5438c37525d",
|
|
15
|
+
"version": "1.0.0.post0.dev145"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
cuqi/experimental/mcmc/_cwmh.py
CHANGED
|
@@ -144,7 +144,7 @@ class CWMHNew(ProposalBasedSamplerNew):
|
|
|
144
144
|
|
|
145
145
|
# Propose a sample x_all_components from the proposal distribution
|
|
146
146
|
# for all the components
|
|
147
|
-
target_eval_t = self.
|
|
147
|
+
target_eval_t = self.current_target_logd
|
|
148
148
|
if isinstance(self.proposal,cuqi.distribution.Distribution):
|
|
149
149
|
x_all_components = self.proposal(
|
|
150
150
|
location= self.current_point, scale=self.scale).sample()
|
|
@@ -175,7 +175,7 @@ class CWMHNew(ProposalBasedSamplerNew):
|
|
|
175
175
|
|
|
176
176
|
x_star = x_t.copy()
|
|
177
177
|
|
|
178
|
-
self.
|
|
178
|
+
self.current_target_logd = target_eval_t
|
|
179
179
|
self.current_point = x_t
|
|
180
180
|
|
|
181
181
|
return acc
|
|
@@ -199,23 +199,3 @@ class CWMHNew(ProposalBasedSamplerNew):
|
|
|
199
199
|
# Update the scale parameter
|
|
200
200
|
self.scale = np.minimum(scale_temp, np.ones(self.dim))
|
|
201
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']
|
|
@@ -60,14 +60,17 @@ class ULANew(SamplerNew): # Refactor to Proposal-based sampler?
|
|
|
60
60
|
A Deblur example can be found in demos/demo27_ULA.py
|
|
61
61
|
# TODO: update demo once sampler merged
|
|
62
62
|
"""
|
|
63
|
+
|
|
64
|
+
_STATE_KEYS = SamplerNew._STATE_KEYS.union({'current_target_logd', 'scale', 'current_target_grad'})
|
|
65
|
+
|
|
63
66
|
def __init__(self, target, scale=1.0, **kwargs):
|
|
64
67
|
|
|
65
68
|
super().__init__(target, **kwargs)
|
|
66
69
|
|
|
67
70
|
self.scale = scale
|
|
68
71
|
self.current_point = self.initial_point
|
|
69
|
-
self.
|
|
70
|
-
self.
|
|
72
|
+
self.current_target_logd = self.target.logd(self.current_point)
|
|
73
|
+
self.current_target_grad = self.target.gradient(self.current_point)
|
|
71
74
|
self._acc = [1] # TODO. Check if we need this
|
|
72
75
|
|
|
73
76
|
def validate_target(self):
|
|
@@ -99,15 +102,15 @@ class ULANew(SamplerNew): # Refactor to Proposal-based sampler?
|
|
|
99
102
|
1 (accepted)
|
|
100
103
|
"""
|
|
101
104
|
self.current_point = x_star
|
|
102
|
-
self.
|
|
103
|
-
self.
|
|
105
|
+
self.current_target_logd = target_eval_star
|
|
106
|
+
self.current_target_grad = target_grad_star
|
|
104
107
|
acc = 1
|
|
105
108
|
return acc
|
|
106
109
|
|
|
107
110
|
def step(self):
|
|
108
111
|
# propose state
|
|
109
112
|
xi = cuqi.distribution.Normal(mean=np.zeros(self.dim), std=np.sqrt(self.scale)).sample()
|
|
110
|
-
x_star = self.current_point + 0.5*self.scale*self.
|
|
113
|
+
x_star = self.current_point + 0.5*self.scale*self.current_target_grad + xi
|
|
111
114
|
|
|
112
115
|
# evaluate target
|
|
113
116
|
target_eval_star, target_grad_star = self.target.logd(x_star), self.target.gradient(x_star)
|
|
@@ -120,26 +123,6 @@ class ULANew(SamplerNew): # Refactor to Proposal-based sampler?
|
|
|
120
123
|
def tune(self, skip_len, update_count):
|
|
121
124
|
pass
|
|
122
125
|
|
|
123
|
-
def get_state(self):
|
|
124
|
-
if isinstance(self.current_point, CUQIarray):
|
|
125
|
-
self.current_point = self.current_point.to_numpy()
|
|
126
|
-
if isinstance(self.current_target_eval, CUQIarray):
|
|
127
|
-
self.current_target_eval = self.current_target_eval.to_numpy()
|
|
128
|
-
if isinstance(self.current_target_grad_eval, CUQIarray):
|
|
129
|
-
self.current_target_grad_eval = self.current_target_grad_eval.to_numpy()
|
|
130
|
-
return {'sampler_type': 'ULA', 'current_point': self.current_point, \
|
|
131
|
-
'current_target_eval': self.current_target_eval, \
|
|
132
|
-
'current_target_grad_eval': self.current_target_grad_eval, \
|
|
133
|
-
'scale': self.scale}
|
|
134
|
-
|
|
135
|
-
def set_state(self, state):
|
|
136
|
-
temp = CUQIarray(state['current_point'] , geometry=self.target.geometry)
|
|
137
|
-
self.current_point = temp
|
|
138
|
-
temp = CUQIarray(state['current_target_eval'] , geometry=self.target.geometry)
|
|
139
|
-
self.current_target_eval = temp
|
|
140
|
-
temp = CUQIarray(state['current_target_grad_eval'] , geometry=self.target.geometry)
|
|
141
|
-
self.current_target_grad_eval = temp
|
|
142
|
-
self.scale = state['scale']
|
|
143
126
|
|
|
144
127
|
class MALANew(ULANew): # Refactor to Proposal-based sampler?
|
|
145
128
|
""" Metropolis-adjusted Langevin algorithm (MALA) (Roberts and Tweedie, 1996)
|
|
@@ -219,9 +202,9 @@ class MALANew(ULANew): # Refactor to Proposal-based sampler?
|
|
|
219
202
|
scaler
|
|
220
203
|
1 if accepted, 0 otherwise
|
|
221
204
|
"""
|
|
222
|
-
log_target_ratio = target_eval_star - self.
|
|
205
|
+
log_target_ratio = target_eval_star - self.current_target_logd
|
|
223
206
|
log_prop_ratio = self._log_proposal(self.current_point, x_star, target_grad_star) \
|
|
224
|
-
- self._log_proposal(x_star, self.current_point, self.
|
|
207
|
+
- self._log_proposal(x_star, self.current_point, self.current_target_grad)
|
|
225
208
|
log_alpha = min(0, log_target_ratio + log_prop_ratio)
|
|
226
209
|
|
|
227
210
|
# accept/reject with Metropolis
|
|
@@ -229,8 +212,8 @@ class MALANew(ULANew): # Refactor to Proposal-based sampler?
|
|
|
229
212
|
log_u = np.log(np.random.rand())
|
|
230
213
|
if (log_u <= log_alpha) and (np.isnan(target_eval_star) == False):
|
|
231
214
|
self.current_point = x_star
|
|
232
|
-
self.
|
|
233
|
-
self.
|
|
215
|
+
self.current_target_logd = target_eval_star
|
|
216
|
+
self.current_target_grad = target_grad_star
|
|
234
217
|
acc = 1
|
|
235
218
|
return acc
|
|
236
219
|
|
|
@@ -241,15 +224,3 @@ class MALANew(ULANew): # Refactor to Proposal-based sampler?
|
|
|
241
224
|
mu = theta_k + ((self.scale)/2)*g_logpi_k
|
|
242
225
|
misfit = theta_star - mu
|
|
243
226
|
return -0.5*((1/(self.scale))*(misfit.T @ misfit))
|
|
244
|
-
|
|
245
|
-
def get_state(self):
|
|
246
|
-
if isinstance(self.current_point, CUQIarray):
|
|
247
|
-
self.current_point = self.current_point.to_numpy()
|
|
248
|
-
if isinstance(self.current_target_eval, CUQIarray):
|
|
249
|
-
self.current_target_eval = self.current_target_eval.to_numpy()
|
|
250
|
-
if isinstance(self.current_target_grad_eval, CUQIarray):
|
|
251
|
-
self.current_target_grad_eval = self.current_target_grad_eval.to_numpy()
|
|
252
|
-
return {'sampler_type': 'MALA', 'current_point': self.current_point, \
|
|
253
|
-
'current_target_eval': self.current_target_eval, \
|
|
254
|
-
'current_target_grad_eval': self.current_target_grad_eval, \
|
|
255
|
-
'scale': self.scale}
|
cuqi/experimental/mcmc/_mh.py
CHANGED
|
@@ -23,6 +23,8 @@ class MHNew(ProposalBasedSamplerNew):
|
|
|
23
23
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
+
_STATE_KEYS = ProposalBasedSamplerNew._STATE_KEYS.union({'scale', '_scale_temp'})
|
|
27
|
+
|
|
26
28
|
def __init__(self, target, proposal=None, scale=1, **kwargs):
|
|
27
29
|
super().__init__(target, proposal=proposal, scale=scale, **kwargs)
|
|
28
30
|
# Due to a bug? in old MH, we must keep track of this extra variable to match behavior.
|
|
@@ -54,7 +56,7 @@ class MHNew(ProposalBasedSamplerNew):
|
|
|
54
56
|
target_eval_star = self.target.logd(x_star)
|
|
55
57
|
|
|
56
58
|
# ratio and acceptance probability
|
|
57
|
-
ratio = target_eval_star - self.
|
|
59
|
+
ratio = target_eval_star - self.current_target_logd # proposal is symmetric
|
|
58
60
|
alpha = min(0, ratio)
|
|
59
61
|
|
|
60
62
|
# accept/reject
|
|
@@ -62,7 +64,7 @@ class MHNew(ProposalBasedSamplerNew):
|
|
|
62
64
|
acc = 0
|
|
63
65
|
if (u_theta <= alpha):
|
|
64
66
|
self.current_point = x_star
|
|
65
|
-
self.
|
|
67
|
+
self.current_target_logd = target_eval_star
|
|
66
68
|
acc = 1
|
|
67
69
|
|
|
68
70
|
return acc
|
|
@@ -79,13 +81,3 @@ class MHNew(ProposalBasedSamplerNew):
|
|
|
79
81
|
|
|
80
82
|
# update parameters
|
|
81
83
|
self.scale = min(self._scale_temp, 1)
|
|
82
|
-
|
|
83
|
-
def get_state(self):
|
|
84
|
-
return {'sampler_type': 'MH', 'current_point': self.current_point.to_numpy(), 'current_target': self.current_target.to_numpy(), 'scale': self.scale}
|
|
85
|
-
|
|
86
|
-
def set_state(self, state):
|
|
87
|
-
temp = CUQIarray(state['current_point'] , geometry=self.target.geometry)
|
|
88
|
-
self.current_point = temp
|
|
89
|
-
temp = CUQIarray(state['current_target'] , geometry=self.target.geometry)
|
|
90
|
-
self.current_target = temp
|
|
91
|
-
self.scale = state['scale']
|
cuqi/experimental/mcmc/_pcn.py
CHANGED
|
@@ -5,13 +5,15 @@ from cuqi.array import CUQIarray
|
|
|
5
5
|
|
|
6
6
|
class pCNNew(SamplerNew): # Refactor to Proposal-based sampler?
|
|
7
7
|
|
|
8
|
+
_STATE_KEYS = SamplerNew._STATE_KEYS.union({'scale', 'current_likelihood_logd'})
|
|
9
|
+
|
|
8
10
|
def __init__(self, target, scale=1.0, **kwargs):
|
|
9
11
|
|
|
10
12
|
super().__init__(target, **kwargs)
|
|
11
13
|
|
|
12
14
|
self.scale = scale
|
|
13
15
|
self.current_point = self.initial_point
|
|
14
|
-
self.
|
|
16
|
+
self.current_likelihood_logd = self._loglikelihood(self.current_point)
|
|
15
17
|
|
|
16
18
|
self._acc = [1] # TODO. Check if we need this
|
|
17
19
|
|
|
@@ -33,7 +35,7 @@ class pCNNew(SamplerNew): # Refactor to Proposal-based sampler?
|
|
|
33
35
|
loglike_eval_star = self._loglikelihood(x_star)
|
|
34
36
|
|
|
35
37
|
# ratio and acceptance probability
|
|
36
|
-
ratio = loglike_eval_star - self.
|
|
38
|
+
ratio = loglike_eval_star - self.current_likelihood_logd # proposal is symmetric
|
|
37
39
|
alpha = min(0, ratio)
|
|
38
40
|
|
|
39
41
|
# accept/reject
|
|
@@ -41,7 +43,7 @@ class pCNNew(SamplerNew): # Refactor to Proposal-based sampler?
|
|
|
41
43
|
u_theta = np.log(np.random.rand())
|
|
42
44
|
if (u_theta <= alpha):
|
|
43
45
|
self.current_point = x_star
|
|
44
|
-
self.
|
|
46
|
+
self.current_likelihood_logd = loglike_eval_star
|
|
45
47
|
acc = 1
|
|
46
48
|
|
|
47
49
|
return acc
|
|
@@ -87,15 +89,3 @@ class pCNNew(SamplerNew): # Refactor to Proposal-based sampler?
|
|
|
87
89
|
|
|
88
90
|
def tune(self, skip_len, update_count):
|
|
89
91
|
pass
|
|
90
|
-
|
|
91
|
-
def get_state(self):
|
|
92
|
-
return {'sampler_type': 'PCN', 'current_point': self.current_point.to_numpy(), \
|
|
93
|
-
'current_loglike_eval': self.current_loglike_eval.to_numpy(), \
|
|
94
|
-
'scale': self.scale}
|
|
95
|
-
|
|
96
|
-
def set_state(self, state):
|
|
97
|
-
temp = CUQIarray(state['current_point'] , geometry=self.target.geometry)
|
|
98
|
-
self.current_point = temp
|
|
99
|
-
temp = CUQIarray(state['current_loglike_eval'] , geometry=self.target.geometry)
|
|
100
|
-
self.current_loglike_eval = temp
|
|
101
|
-
self.scale = state['scale']
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import scipy as sp
|
|
2
|
+
from scipy.linalg.interpolative import estimate_spectral_norm
|
|
3
|
+
from scipy.sparse.linalg import LinearOperator as scipyLinearOperator
|
|
4
|
+
import numpy as np
|
|
5
|
+
import cuqi
|
|
6
|
+
from cuqi.solver import CGLS, FISTA
|
|
7
|
+
from cuqi.experimental.mcmc import SamplerNew
|
|
8
|
+
from cuqi.array import CUQIarray
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LinearRTONew(SamplerNew):
|
|
12
|
+
"""
|
|
13
|
+
Linear RTO (Randomize-Then-Optimize) sampler.
|
|
14
|
+
|
|
15
|
+
Samples posterior related to the inverse problem with Gaussian likelihood and prior, and where the forward model is Linear.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
------------
|
|
19
|
+
target : `cuqi.distribution.Posterior`, `cuqi.distribution.MultipleLikelihoodPosterior` or 5-dimensional tuple.
|
|
20
|
+
If target is of type cuqi.distribution.Posterior or cuqi.distribution.MultipleLikelihoodPosterior, it represents the posterior distribution.
|
|
21
|
+
If target is a 5-dimensional tuple, it assumes the following structure:
|
|
22
|
+
(data, model, L_sqrtprec, P_mean, P_sqrtrec)
|
|
23
|
+
|
|
24
|
+
Here:
|
|
25
|
+
data: is a m-dimensional numpy array containing the measured data.
|
|
26
|
+
model: is a m by n dimensional matrix or LinearModel representing the forward model.
|
|
27
|
+
L_sqrtprec: is the squareroot of the precision matrix of the Gaussian likelihood.
|
|
28
|
+
P_mean: is the prior mean.
|
|
29
|
+
P_sqrtprec: is the squareroot of the precision matrix of the Gaussian mean.
|
|
30
|
+
|
|
31
|
+
initial_point : `np.ndarray`
|
|
32
|
+
Initial point for the sampler. *Optional*.
|
|
33
|
+
|
|
34
|
+
maxit : int
|
|
35
|
+
Maximum number of iterations of the inner CGLS solver. *Optional*.
|
|
36
|
+
|
|
37
|
+
tol : float
|
|
38
|
+
Tolerance of the inner CGLS solver. *Optional*.
|
|
39
|
+
|
|
40
|
+
callback : callable, *Optional*
|
|
41
|
+
If set this function will be called after every sample.
|
|
42
|
+
The signature of the callback function is `callback(sample, sample_index)`,
|
|
43
|
+
where `sample` is the current sample and `sample_index` is the index of the sample.
|
|
44
|
+
An example is shown in demos/demo31_callback.py.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
def __init__(self, target, initial_point=None, maxit=10, tol=1e-6, **kwargs):
|
|
48
|
+
|
|
49
|
+
super().__init__(target=target, initial_point=initial_point, **kwargs)
|
|
50
|
+
|
|
51
|
+
if initial_point is None: #TODO: Replace later with a getter
|
|
52
|
+
self.initial_point = np.zeros(self.dim)
|
|
53
|
+
self._samples = [self.initial_point]
|
|
54
|
+
|
|
55
|
+
self.current_point = self.initial_point
|
|
56
|
+
self._acc = [1] # TODO. Check if we need this
|
|
57
|
+
|
|
58
|
+
# Other parameters
|
|
59
|
+
self.maxit = maxit
|
|
60
|
+
self.tol = tol
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def prior(self):
|
|
64
|
+
return self.target.prior
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def likelihood(self):
|
|
68
|
+
return self.target.likelihood
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def likelihoods(self):
|
|
72
|
+
if isinstance(self.target, cuqi.distribution.Posterior):
|
|
73
|
+
return [self.target.likelihood]
|
|
74
|
+
elif isinstance(self.target, cuqi.distribution.MultipleLikelihoodPosterior):
|
|
75
|
+
return self.target.likelihoods
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def model(self):
|
|
79
|
+
return self.target.model
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def data(self):
|
|
83
|
+
return self.target.data
|
|
84
|
+
|
|
85
|
+
@SamplerNew.target.setter
|
|
86
|
+
def target(self, value):
|
|
87
|
+
""" Set the target density. Runs validation of the target. """
|
|
88
|
+
# Accept tuple of inputs and construct posterior
|
|
89
|
+
if isinstance(value, tuple) and len(value) == 5:
|
|
90
|
+
# Structure (data, model, L_sqrtprec, P_mean, P_sqrtprec)
|
|
91
|
+
data = value[0]
|
|
92
|
+
model = value[1]
|
|
93
|
+
L_sqrtprec = value[2]
|
|
94
|
+
P_mean = value[3]
|
|
95
|
+
P_sqrtprec = value[4]
|
|
96
|
+
|
|
97
|
+
# If numpy matrix convert to CUQI model
|
|
98
|
+
if isinstance(model, np.ndarray) and len(model.shape) == 2:
|
|
99
|
+
model = cuqi.model.LinearModel(model)
|
|
100
|
+
|
|
101
|
+
# Check model input
|
|
102
|
+
if not isinstance(model, cuqi.model.LinearModel):
|
|
103
|
+
raise TypeError("Model needs to be cuqi.model.LinearModel or matrix")
|
|
104
|
+
|
|
105
|
+
# Likelihood
|
|
106
|
+
L = cuqi.distribution.Gaussian(model, sqrtprec=L_sqrtprec).to_likelihood(data)
|
|
107
|
+
|
|
108
|
+
# Prior TODO: allow multiple priors stacked
|
|
109
|
+
#if isinstance(P_mean, list) and isinstance(P_sqrtprec, list):
|
|
110
|
+
# P = cuqi.distribution.JointGaussianSqrtPrec(P_mean, P_sqrtprec)
|
|
111
|
+
#else:
|
|
112
|
+
P = cuqi.distribution.Gaussian(P_mean, sqrtprec=P_sqrtprec)
|
|
113
|
+
|
|
114
|
+
# Construct posterior
|
|
115
|
+
value = cuqi.distribution.Posterior(L, P)
|
|
116
|
+
super(LinearRTONew, type(self)).target.fset(self, value)
|
|
117
|
+
self._precompute()
|
|
118
|
+
|
|
119
|
+
def _precompute(self):
|
|
120
|
+
L1 = [likelihood.distribution.sqrtprec for likelihood in self.likelihoods]
|
|
121
|
+
L2 = self.prior.sqrtprec
|
|
122
|
+
L2mu = self.prior.sqrtprecTimesMean
|
|
123
|
+
|
|
124
|
+
# pre-computations
|
|
125
|
+
self.n = self.prior.dim
|
|
126
|
+
self.b_tild = np.hstack([L@likelihood.data for (L, likelihood) in zip(L1, self.likelihoods)]+ [L2mu])
|
|
127
|
+
|
|
128
|
+
callability = [callable(likelihood.model) for likelihood in self.likelihoods]
|
|
129
|
+
notcallability = [not c for c in callability]
|
|
130
|
+
if all(notcallability):
|
|
131
|
+
self.M = sp.sparse.vstack([L@likelihood.model for (L, likelihood) in zip(L1, self.likelihoods)] + [L2])
|
|
132
|
+
elif all(callability):
|
|
133
|
+
# in this case, model is a function doing forward and backward operations
|
|
134
|
+
def M(x, flag):
|
|
135
|
+
if flag == 1:
|
|
136
|
+
out1 = [L @ likelihood.model.forward(x) for (L, likelihood) in zip(L1, self.likelihoods)]
|
|
137
|
+
out2 = L2 @ x
|
|
138
|
+
out = np.hstack(out1 + [out2])
|
|
139
|
+
elif flag == 2:
|
|
140
|
+
idx_start = 0
|
|
141
|
+
idx_end = 0
|
|
142
|
+
out1 = np.zeros(self.n)
|
|
143
|
+
for likelihood in self.likelihoods:
|
|
144
|
+
idx_end += len(likelihood.data)
|
|
145
|
+
out1 += likelihood.model.adjoint(likelihood.distribution.sqrtprec.T@x[idx_start:idx_end])
|
|
146
|
+
idx_start = idx_end
|
|
147
|
+
out2 = L2.T @ x[idx_end:]
|
|
148
|
+
out = out1 + out2
|
|
149
|
+
return out
|
|
150
|
+
self.M = M
|
|
151
|
+
else:
|
|
152
|
+
raise TypeError("All likelihoods need to be callable or none need to be callable.")
|
|
153
|
+
|
|
154
|
+
def step(self):
|
|
155
|
+
y = self.b_tild + np.random.randn(len(self.b_tild))
|
|
156
|
+
sim = CGLS(self.M, y, self.current_point, self.maxit, self.tol)
|
|
157
|
+
self.current_point, _ = sim.solve()
|
|
158
|
+
acc = 1
|
|
159
|
+
return acc
|
|
160
|
+
|
|
161
|
+
def tune(self, skip_len, update_count):
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
def validate_target(self):
|
|
165
|
+
# Check target type
|
|
166
|
+
if not isinstance(self.target, (cuqi.distribution.Posterior, cuqi.distribution.MultipleLikelihoodPosterior)):
|
|
167
|
+
raise ValueError(f"To initialize an object of type {self.__class__}, 'target' need to be of type 'cuqi.distribution.Posterior' or 'cuqi.distribution.MultipleLikelihoodPosterior'.")
|
|
168
|
+
|
|
169
|
+
# Check Linear model and Gaussian likelihood(s)
|
|
170
|
+
if isinstance(self.target, cuqi.distribution.Posterior):
|
|
171
|
+
if not isinstance(self.model, cuqi.model.LinearModel):
|
|
172
|
+
raise TypeError("Model needs to be linear")
|
|
173
|
+
|
|
174
|
+
if not hasattr(self.likelihood.distribution, "sqrtprec"):
|
|
175
|
+
raise TypeError("Distribution in Likelihood must contain a sqrtprec attribute")
|
|
176
|
+
|
|
177
|
+
elif isinstance(self.target, cuqi.distribution.MultipleLikelihoodPosterior): # Elif used for further alternatives, e.g., stacked posterior
|
|
178
|
+
for likelihood in self.likelihoods:
|
|
179
|
+
if not isinstance(likelihood.model, cuqi.model.LinearModel):
|
|
180
|
+
raise TypeError("Model needs to be linear")
|
|
181
|
+
|
|
182
|
+
if not hasattr(likelihood.distribution, "sqrtprec"):
|
|
183
|
+
raise TypeError("Distribution in Likelihood must contain a sqrtprec attribute")
|
|
184
|
+
|
|
185
|
+
# Check Gaussian prior
|
|
186
|
+
if not hasattr(self.prior, "sqrtprec"):
|
|
187
|
+
raise TypeError("prior must contain a sqrtprec attribute")
|
|
188
|
+
|
|
189
|
+
if not hasattr(self.prior, "sqrtprecTimesMean"):
|
|
190
|
+
raise TypeError("Prior must contain a sqrtprecTimesMean attribute")
|
|
191
|
+
|
|
192
|
+
class RegularizedLinearRTONew(LinearRTONew):
|
|
193
|
+
"""
|
|
194
|
+
Regularized Linear RTO (Randomize-Then-Optimize) sampler.
|
|
195
|
+
|
|
196
|
+
Samples posterior related to the inverse problem with Gaussian likelihood and implicit Gaussian prior, and where the forward model is Linear.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
------------
|
|
200
|
+
target : `cuqi.distribution.Posterior`
|
|
201
|
+
See `cuqi.sampler.LinearRTO`
|
|
202
|
+
|
|
203
|
+
initial_point : `np.ndarray`
|
|
204
|
+
Initial point for the sampler. *Optional*.
|
|
205
|
+
|
|
206
|
+
maxit : int
|
|
207
|
+
Maximum number of iterations of the inner FISTA solver. *Optional*.
|
|
208
|
+
|
|
209
|
+
stepsize : string or float
|
|
210
|
+
If stepsize is a string and equals either "automatic", then the stepsize is automatically estimated based on the spectral norm.
|
|
211
|
+
If stepsize is a float, then this stepsize is used.
|
|
212
|
+
|
|
213
|
+
abstol : float
|
|
214
|
+
Absolute tolerance of the inner FISTA solver. *Optional*.
|
|
215
|
+
|
|
216
|
+
adaptive : bool
|
|
217
|
+
If True, FISTA is used as inner solver, otherwise ISTA is used. *Optional*.
|
|
218
|
+
|
|
219
|
+
callback : callable, *Optional*
|
|
220
|
+
If set this function will be called after every sample.
|
|
221
|
+
The signature of the callback function is `callback(sample, sample_index)`,
|
|
222
|
+
where `sample` is the current sample and `sample_index` is the index of the sample.
|
|
223
|
+
An example is shown in demos/demo31_callback.py.
|
|
224
|
+
|
|
225
|
+
"""
|
|
226
|
+
def __init__(self, target, initial_point=None, maxit=100, stepsize="automatic", abstol=1e-10, adaptive=True, **kwargs):
|
|
227
|
+
|
|
228
|
+
super().__init__(target=target, initial_point=initial_point, **kwargs)
|
|
229
|
+
|
|
230
|
+
# Other parameters
|
|
231
|
+
self.stepsize = stepsize
|
|
232
|
+
self.abstol = abstol
|
|
233
|
+
self.adaptive = adaptive
|
|
234
|
+
self.proximal = target.prior.proximal
|
|
235
|
+
self._stepsize = self._choose_stepsize()
|
|
236
|
+
self.maxit = maxit
|
|
237
|
+
|
|
238
|
+
@LinearRTONew.target.setter
|
|
239
|
+
def target(self, value):
|
|
240
|
+
if not callable(value.prior.proximal):
|
|
241
|
+
raise TypeError("Projector needs to be callable")
|
|
242
|
+
return super(RegularizedLinearRTONew, type(self)).target.fset(self, value)
|
|
243
|
+
|
|
244
|
+
def _choose_stepsize(self):
|
|
245
|
+
if isinstance(self.stepsize, str):
|
|
246
|
+
if self.stepsize in ["automatic"]:
|
|
247
|
+
if not callable(self.M):
|
|
248
|
+
M_op = scipyLinearOperator(self.M.shape, matvec = lambda v: self.M@v, rmatvec = lambda w: self.M.T@w)
|
|
249
|
+
else:
|
|
250
|
+
M_op = scipyLinearOperator((len(self.b_tild), self.n), matvec = lambda v: self.M(v,1), rmatvec = lambda w: self.M(w,2))
|
|
251
|
+
|
|
252
|
+
_stepsize = 0.99/(estimate_spectral_norm(M_op)**2)
|
|
253
|
+
# print(f"Estimated stepsize for regularized Linear RTO: {_stepsize}")
|
|
254
|
+
else:
|
|
255
|
+
raise ValueError("Stepsize choice not supported")
|
|
256
|
+
else:
|
|
257
|
+
_stepsize = self.stepsize
|
|
258
|
+
return _stepsize
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def prior(self):
|
|
262
|
+
return self.target.prior.gaussian
|
|
263
|
+
|
|
264
|
+
def step(self):
|
|
265
|
+
y = self.b_tild + np.random.randn(len(self.b_tild))
|
|
266
|
+
sim = FISTA(self.M, y, self.current_point, self.proximal,
|
|
267
|
+
maxit = self.maxit, stepsize = self._stepsize, abstol = self.abstol, adaptive = self.adaptive)
|
|
268
|
+
self.current_point, _ = sim.solve()
|
|
269
|
+
acc = 1
|
|
270
|
+
return acc
|
|
@@ -21,6 +21,11 @@ class SamplerNew(ABC):
|
|
|
21
21
|
Samples are stored in a list to allow for dynamic growth of the sample set. Returning samples is done by creating a new Samples object from the list of samples.
|
|
22
22
|
|
|
23
23
|
"""
|
|
24
|
+
_STATE_KEYS = {'current_point'}
|
|
25
|
+
""" Set of keys for the state dictionary. """
|
|
26
|
+
|
|
27
|
+
_HISTORY_KEYS = {'_samples', '_acc'}
|
|
28
|
+
""" Set of keys for the history dictionary. """
|
|
24
29
|
|
|
25
30
|
def __init__(self, target: cuqi.density.Density, initial_point=None, callback=None):
|
|
26
31
|
""" Initializer for abstract base class for all samplers.
|
|
@@ -45,8 +50,10 @@ class SamplerNew(ABC):
|
|
|
45
50
|
# Choose initial point if not given
|
|
46
51
|
if initial_point is None:
|
|
47
52
|
initial_point = np.ones(self.dim)
|
|
53
|
+
|
|
54
|
+
self.initial_point = initial_point
|
|
48
55
|
|
|
49
|
-
self._samples = [initial_point]
|
|
56
|
+
self._samples = [initial_point] # Remove. See #324.
|
|
50
57
|
|
|
51
58
|
# ------------ Abstract methods to be implemented by subclasses ------------
|
|
52
59
|
|
|
@@ -65,24 +72,7 @@ class SamplerNew(ABC):
|
|
|
65
72
|
""" Validate the target is compatible with the sampler. Called when the target is set. Should raise an error if the target is not compatible. """
|
|
66
73
|
pass
|
|
67
74
|
|
|
68
|
-
@abstractmethod
|
|
69
|
-
def get_state(self):
|
|
70
|
-
""" Return the state of the sampler. """
|
|
71
|
-
pass
|
|
72
|
-
|
|
73
|
-
@abstractmethod
|
|
74
|
-
def set_state(self, state):
|
|
75
|
-
""" Set the state of the sampler. """
|
|
76
|
-
pass
|
|
77
|
-
|
|
78
|
-
|
|
79
75
|
# ------------ Public attributes ------------
|
|
80
|
-
|
|
81
|
-
@property
|
|
82
|
-
def initial_point(self):
|
|
83
|
-
""" Return the initial point of the sampler. This is always the first sample. """
|
|
84
|
-
return self._samples[0]
|
|
85
|
-
|
|
86
76
|
@property
|
|
87
77
|
def dim(self):
|
|
88
78
|
""" Dimension of the target density. """
|
|
@@ -104,6 +94,15 @@ class SamplerNew(ABC):
|
|
|
104
94
|
self._target = value
|
|
105
95
|
self.validate_target()
|
|
106
96
|
|
|
97
|
+
@property
|
|
98
|
+
def current_point(self):
|
|
99
|
+
""" The current point of the sampler. """
|
|
100
|
+
return self._current_point
|
|
101
|
+
|
|
102
|
+
@current_point.setter
|
|
103
|
+
def current_point(self, value):
|
|
104
|
+
""" Set the current point of the sampler. """
|
|
105
|
+
self._current_point = value
|
|
107
106
|
|
|
108
107
|
# ------------ Public methods ------------
|
|
109
108
|
|
|
@@ -204,6 +203,94 @@ class SamplerNew(ABC):
|
|
|
204
203
|
self._call_callback(self.current_point, len(self._samples)-1)
|
|
205
204
|
|
|
206
205
|
return self
|
|
206
|
+
|
|
207
|
+
def get_state(self) -> dict:
|
|
208
|
+
""" Return the state of the sampler.
|
|
209
|
+
|
|
210
|
+
The state is used when checkpointing the sampler.
|
|
211
|
+
|
|
212
|
+
The state of the sampler is a dictionary with keys 'metadata' and 'state'.
|
|
213
|
+
The 'metadata' key contains information about the sampler type.
|
|
214
|
+
The 'state' key contains the state of the sampler.
|
|
215
|
+
|
|
216
|
+
For example, the state of a "MH" sampler could be:
|
|
217
|
+
|
|
218
|
+
state = {
|
|
219
|
+
'metadata': {
|
|
220
|
+
'sampler_type': 'MH'
|
|
221
|
+
},
|
|
222
|
+
'state': {
|
|
223
|
+
'current_point': np.array([...]),
|
|
224
|
+
'current_target_logd': -123.45,
|
|
225
|
+
'scale': 1.0,
|
|
226
|
+
...
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
"""
|
|
230
|
+
state = {
|
|
231
|
+
'metadata': {
|
|
232
|
+
'sampler_type': self.__class__.__name__
|
|
233
|
+
},
|
|
234
|
+
'state': {
|
|
235
|
+
key: getattr(self, key) for key in self._STATE_KEYS
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return state
|
|
239
|
+
|
|
240
|
+
def set_state(self, state: dict):
|
|
241
|
+
""" Set the state of the sampler.
|
|
242
|
+
|
|
243
|
+
The state is used when loading the sampler from a checkpoint.
|
|
244
|
+
|
|
245
|
+
The state of the sampler is a dictionary with keys 'metadata' and 'state'.
|
|
246
|
+
|
|
247
|
+
For example, the state of a "MH" sampler could be:
|
|
248
|
+
|
|
249
|
+
state = {
|
|
250
|
+
'metadata': {
|
|
251
|
+
'sampler_type': 'MH'
|
|
252
|
+
},
|
|
253
|
+
'state': {
|
|
254
|
+
'current_point': np.array([...]),
|
|
255
|
+
'current_target_logd': -123.45,
|
|
256
|
+
'scale': 1.0,
|
|
257
|
+
...
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
"""
|
|
261
|
+
if state['metadata']['sampler_type'] != self.__class__.__name__:
|
|
262
|
+
raise ValueError(f"Sampler type in state dictionary ({state['metadata']['sampler_type']}) does not match the type of the sampler ({self.__class__.__name__}).")
|
|
263
|
+
|
|
264
|
+
for key, value in state['state'].items():
|
|
265
|
+
if key in self._STATE_KEYS:
|
|
266
|
+
setattr(self, key, value)
|
|
267
|
+
else:
|
|
268
|
+
raise ValueError(f"Key {key} not recognized in state dictionary of sampler {self.__class__.__name__}.")
|
|
269
|
+
|
|
270
|
+
def get_history(self) -> dict:
|
|
271
|
+
""" Return the history of the sampler. """
|
|
272
|
+
history = {
|
|
273
|
+
'metadata': {
|
|
274
|
+
'sampler_type': self.__class__.__name__
|
|
275
|
+
},
|
|
276
|
+
'history': {
|
|
277
|
+
key: getattr(self, key) for key in self._HISTORY_KEYS
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return history
|
|
281
|
+
|
|
282
|
+
def set_history(self, history: dict):
|
|
283
|
+
""" Set the history of the sampler. """
|
|
284
|
+
if history['metadata']['sampler_type'] != self.__class__.__name__:
|
|
285
|
+
raise ValueError(f"Sampler type in history dictionary ({history['metadata']['sampler_type']}) does not match the type of the sampler ({self.__class__.__name__}).")
|
|
286
|
+
|
|
287
|
+
for key, value in history['history'].items():
|
|
288
|
+
if key in self._HISTORY_KEYS:
|
|
289
|
+
setattr(self, key, value)
|
|
290
|
+
else:
|
|
291
|
+
raise ValueError(f"Key {key} not recognized in history dictionary of sampler {self.__class__.__name__}.")
|
|
292
|
+
|
|
293
|
+
# ------------ Private methods ------------
|
|
207
294
|
|
|
208
295
|
def _call_callback(self, sample, sample_index):
|
|
209
296
|
""" Calls the callback function. Assumes input is sample and sample index"""
|
|
@@ -213,6 +300,9 @@ class SamplerNew(ABC):
|
|
|
213
300
|
|
|
214
301
|
class ProposalBasedSamplerNew(SamplerNew, ABC):
|
|
215
302
|
""" Abstract base class for samplers that use a proposal distribution. """
|
|
303
|
+
|
|
304
|
+
_STATE_KEYS = SamplerNew._STATE_KEYS.union({'current_target_logd', 'scale'})
|
|
305
|
+
|
|
216
306
|
def __init__(self, target, proposal=None, scale=1, **kwargs):
|
|
217
307
|
""" Initializer for proposal based samplers.
|
|
218
308
|
|
|
@@ -235,7 +325,7 @@ class ProposalBasedSamplerNew(SamplerNew, ABC):
|
|
|
235
325
|
super().__init__(target, **kwargs)
|
|
236
326
|
|
|
237
327
|
self.current_point = self.initial_point
|
|
238
|
-
self.
|
|
328
|
+
self.current_target_logd = self.target.logd(self.current_point)
|
|
239
329
|
self.proposal = proposal
|
|
240
330
|
self.scale = scale
|
|
241
331
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|