inference-tools 0.13.2__py3-none-any.whl → 0.13.4__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.
- inference/_version.py +2 -2
- inference/approx/conditional.py +3 -3
- inference/gp/covariance.py +34 -34
- inference/gp/mean.py +5 -5
- inference/gp/optimisation.py +9 -4
- inference/gp/regression.py +42 -40
- inference/likelihoods.py +20 -12
- inference/mcmc/base.py +5 -3
- inference/mcmc/gibbs.py +12 -9
- inference/mcmc/hmc.py +8 -4
- inference/mcmc/pca.py +5 -5
- inference/mcmc/utilities.py +9 -9
- inference/pdf/kde.py +3 -3
- inference/pdf/unimodal.py +54 -44
- inference/posterior.py +6 -4
- inference/priors.py +255 -120
- {inference_tools-0.13.2.dist-info → inference_tools-0.13.4.dist-info}/METADATA +9 -9
- inference_tools-0.13.4.dist-info/RECORD +33 -0
- {inference_tools-0.13.2.dist-info → inference_tools-0.13.4.dist-info}/WHEEL +1 -1
- inference_tools-0.13.2.dist-info/RECORD +0 -33
- {inference_tools-0.13.2.dist-info → inference_tools-0.13.4.dist-info}/LICENSE +0 -0
- {inference_tools-0.13.2.dist-info → inference_tools-0.13.4.dist-info}/top_level.txt +0 -0
inference/mcmc/gibbs.py
CHANGED
|
@@ -120,7 +120,7 @@ class Parameter:
|
|
|
120
120
|
else:
|
|
121
121
|
return self.upper - d % self.width
|
|
122
122
|
|
|
123
|
-
def submit_accept_prob(self, p):
|
|
123
|
+
def submit_accept_prob(self, p: float):
|
|
124
124
|
self.num += 1
|
|
125
125
|
self.avg += p
|
|
126
126
|
self.var += p * (1 - p)
|
|
@@ -375,23 +375,26 @@ class MetropolisChain(MarkovChain):
|
|
|
375
375
|
ind = argmax(self.probs)
|
|
376
376
|
return array([p.samples[ind] for p in self.params])
|
|
377
377
|
|
|
378
|
-
def set_non_negative(self, parameter, flag=True):
|
|
378
|
+
def set_non_negative(self, parameter: int, flag=True):
|
|
379
379
|
"""
|
|
380
380
|
Constrain a particular parameter to have non-negative values.
|
|
381
381
|
|
|
382
|
-
:param int parameter:
|
|
383
|
-
|
|
382
|
+
:param int parameter: \
|
|
383
|
+
Index of the parameter which is to be set as non-negative.
|
|
384
384
|
"""
|
|
385
385
|
self.params[parameter].non_negative = flag
|
|
386
386
|
|
|
387
|
-
def set_boundaries(
|
|
387
|
+
def set_boundaries(
|
|
388
|
+
self, parameter: int, boundaries: tuple[float, float], remove=False
|
|
389
|
+
):
|
|
388
390
|
"""
|
|
389
391
|
Constrain the value of a particular parameter to specified boundaries.
|
|
390
392
|
|
|
391
|
-
:param int parameter:
|
|
392
|
-
|
|
393
|
+
:param int parameter: \
|
|
394
|
+
Index of the parameter for which boundaries are to be set.
|
|
393
395
|
|
|
394
|
-
:param boundaries:
|
|
396
|
+
:param boundaries: \
|
|
397
|
+
Tuple of boundaries in the format (lower_limit, upper_limit)
|
|
395
398
|
"""
|
|
396
399
|
if remove:
|
|
397
400
|
self.params[parameter].remove_boundaries()
|
|
@@ -402,7 +405,7 @@ class MetropolisChain(MarkovChain):
|
|
|
402
405
|
"""
|
|
403
406
|
Plot diagnostic traces that give information on how the chain is progressing.
|
|
404
407
|
|
|
405
|
-
Currently this method plots:
|
|
408
|
+
Currently, this method plots:
|
|
406
409
|
|
|
407
410
|
- The posterior log-probability as a function of step number, which is useful
|
|
408
411
|
for checking if the chain has reached a maximum. Any early parts of the chain
|
inference/mcmc/hmc.py
CHANGED
|
@@ -145,8 +145,8 @@ class HamiltonianChain(MarkovChain):
|
|
|
145
145
|
else:
|
|
146
146
|
raise ValueError(
|
|
147
147
|
f"""\n
|
|
148
|
-
[ HamiltonianChain error ]
|
|
149
|
-
>> Failed to take step within maximum allowed attempts of {self.max_attempts}
|
|
148
|
+
\r[ HamiltonianChain error ]
|
|
149
|
+
\r>> Failed to take step within maximum allowed attempts of {self.max_attempts}
|
|
150
150
|
"""
|
|
151
151
|
)
|
|
152
152
|
|
|
@@ -155,7 +155,9 @@ class HamiltonianChain(MarkovChain):
|
|
|
155
155
|
self.leapfrog_steps.append(steps_taken)
|
|
156
156
|
self.chain_length += 1
|
|
157
157
|
|
|
158
|
-
def standard_leapfrog(
|
|
158
|
+
def standard_leapfrog(
|
|
159
|
+
self, t: ndarray, r: ndarray, n_steps: int
|
|
160
|
+
) -> tuple[ndarray, ndarray]:
|
|
159
161
|
t_step = self.inv_mass * self.ES.epsilon
|
|
160
162
|
r_step = self.inv_temp * self.ES.epsilon
|
|
161
163
|
r += (0.5 * r_step) * self.grad(t)
|
|
@@ -166,7 +168,9 @@ class HamiltonianChain(MarkovChain):
|
|
|
166
168
|
r += (0.5 * r_step) * self.grad(t)
|
|
167
169
|
return t, r
|
|
168
170
|
|
|
169
|
-
def bounded_leapfrog(
|
|
171
|
+
def bounded_leapfrog(
|
|
172
|
+
self, t: ndarray, r: ndarray, n_steps: int
|
|
173
|
+
) -> tuple[ndarray, ndarray]:
|
|
170
174
|
t_step = self.inv_mass * self.ES.epsilon
|
|
171
175
|
r_step = self.inv_temp * self.ES.epsilon
|
|
172
176
|
r += (0.5 * r_step) * self.grad(t)
|
inference/mcmc/pca.py
CHANGED
|
@@ -33,13 +33,13 @@ class PcaChain(MetropolisChain):
|
|
|
33
33
|
and returns the posterior log-probability.
|
|
34
34
|
|
|
35
35
|
:param start: \
|
|
36
|
-
|
|
37
|
-
at which the chain will start.
|
|
36
|
+
Values of the model parameters as a ``numpy.ndarray`` which correspond to the
|
|
37
|
+
parameter-space coordinates at which the chain will start.
|
|
38
38
|
|
|
39
39
|
:param widths: \
|
|
40
|
-
|
|
41
|
-
the proposal distribution for each model parameter. If not
|
|
42
|
-
widths will be approximated as 5% of the values in 'start'.
|
|
40
|
+
A ``numpy.ndarray`` of standard deviations which serve as initial guesses for
|
|
41
|
+
the widths of the proposal distribution for each model parameter. If not
|
|
42
|
+
specified, the starting widths will be approximated as 5% of the values in 'start'.
|
|
43
43
|
|
|
44
44
|
:param bounds: \
|
|
45
45
|
An instance of the ``inference.mcmc.Bounds`` class, or a sequence of two
|
inference/mcmc/utilities.py
CHANGED
|
@@ -103,26 +103,26 @@ class Bounds:
|
|
|
103
103
|
if self.lower.ndim > 1 or self.upper.ndim > 1:
|
|
104
104
|
raise ValueError(
|
|
105
105
|
f"""\n
|
|
106
|
-
[ {error_source} error ]
|
|
107
|
-
>> Lower and upper bounds must be one-dimensional arrays, but
|
|
108
|
-
>> instead have dimensions {self.lower.ndim} and {self.upper.ndim} respectively.
|
|
106
|
+
\r[ {error_source} error ]
|
|
107
|
+
\r>> Lower and upper bounds must be one-dimensional arrays, but
|
|
108
|
+
\r>> instead have dimensions {self.lower.ndim} and {self.upper.ndim} respectively.
|
|
109
109
|
"""
|
|
110
110
|
)
|
|
111
111
|
|
|
112
112
|
if self.lower.size != self.upper.size:
|
|
113
113
|
raise ValueError(
|
|
114
114
|
f"""\n
|
|
115
|
-
[ {error_source} error ]
|
|
116
|
-
>> Lower and upper bounds must be arrays of equal size, but
|
|
117
|
-
>> instead have sizes {self.lower.size} and {self.upper.size} respectively.
|
|
115
|
+
\r[ {error_source} error ]
|
|
116
|
+
\r>> Lower and upper bounds must be arrays of equal size, but
|
|
117
|
+
\r>> instead have sizes {self.lower.size} and {self.upper.size} respectively.
|
|
118
118
|
"""
|
|
119
119
|
)
|
|
120
120
|
|
|
121
121
|
if (self.lower >= self.upper).any():
|
|
122
122
|
raise ValueError(
|
|
123
123
|
f"""\n
|
|
124
|
-
[ {error_source} error ]
|
|
125
|
-
>> All given upper bounds must be larger than the corresponding lower bounds.
|
|
124
|
+
\r[ {error_source} error ]
|
|
125
|
+
\r>> All given upper bounds must be larger than the corresponding lower bounds.
|
|
126
126
|
"""
|
|
127
127
|
)
|
|
128
128
|
|
|
@@ -152,7 +152,7 @@ class Bounds:
|
|
|
152
152
|
n = q % 2
|
|
153
153
|
return self.lower + (1 - 2 * n) * rem + n * self.width
|
|
154
154
|
|
|
155
|
-
def reflect_momenta(self, theta: ndarray):
|
|
155
|
+
def reflect_momenta(self, theta: ndarray) -> tuple[ndarray, ndarray]:
|
|
156
156
|
q, rem = np_divmod(theta - self.lower, self.width)
|
|
157
157
|
n = q % 2
|
|
158
158
|
reflection = 1 - 2 * n
|
inference/pdf/kde.py
CHANGED
|
@@ -42,8 +42,8 @@ class GaussianKDE(DensityEstimator):
|
|
|
42
42
|
def __init__(
|
|
43
43
|
self,
|
|
44
44
|
sample: ndarray,
|
|
45
|
-
bandwidth=None,
|
|
46
|
-
cross_validation=False,
|
|
45
|
+
bandwidth: float = None,
|
|
46
|
+
cross_validation: bool = False,
|
|
47
47
|
max_cv_samples=5000,
|
|
48
48
|
):
|
|
49
49
|
self.sample = sort(array(sample).flatten()) # sorted array of the samples
|
|
@@ -136,7 +136,7 @@ class GaussianKDE(DensityEstimator):
|
|
|
136
136
|
# A simple estimate which assumes the distribution close to a Gaussian
|
|
137
137
|
return 1.06 * std(self.sample) / (self.sample.size**0.2)
|
|
138
138
|
|
|
139
|
-
def cross_validation_bandwidth_estimator(self, initial_h):
|
|
139
|
+
def cross_validation_bandwidth_estimator(self, initial_h: float) -> float:
|
|
140
140
|
"""
|
|
141
141
|
Selects the bandwidth by maximising a log-probability derived
|
|
142
142
|
using a 'leave-one-out cross-validation' approach.
|
inference/pdf/unimodal.py
CHANGED
|
@@ -4,6 +4,7 @@ from numpy import array, ndarray, linspace, zeros, atleast_1d
|
|
|
4
4
|
from scipy.integrate import simpson, quad
|
|
5
5
|
from scipy.optimize import minimize
|
|
6
6
|
from inference.pdf.base import DensityEstimator
|
|
7
|
+
from inference.pdf.hdi import sample_hdi
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class UnimodalPdf(DensityEstimator):
|
|
@@ -21,9 +22,9 @@ class UnimodalPdf(DensityEstimator):
|
|
|
21
22
|
|
|
22
23
|
def __init__(self, sample: ndarray):
|
|
23
24
|
self.sample = array(sample).flatten()
|
|
24
|
-
self.n_samps = sample.size
|
|
25
|
+
self.n_samps = self.sample.size
|
|
25
26
|
|
|
26
|
-
# chebyshev
|
|
27
|
+
# chebyshev quadrature weights and axes
|
|
27
28
|
self.sd = 0.2
|
|
28
29
|
self.n_nodes = 128
|
|
29
30
|
k = linspace(1, self.n_nodes, self.n_nodes)
|
|
@@ -34,27 +35,31 @@ class UnimodalPdf(DensityEstimator):
|
|
|
34
35
|
# first minimise based on a slice of the sample, if it's large enough
|
|
35
36
|
self.cutoff = 2000
|
|
36
37
|
self.skip = max(self.n_samps // self.cutoff, 1)
|
|
37
|
-
|
|
38
|
-
self.x = self.sample[:: self.skip]
|
|
39
|
-
self.n = len(self.x)
|
|
38
|
+
self.fitted_samples = self.sample[:: self.skip]
|
|
40
39
|
|
|
41
40
|
# makes guesses based on sample moments
|
|
42
|
-
guesses = self.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
guesses = sorted(guesses, key=minfunc)
|
|
41
|
+
guesses, self.bounds = self.generate_guesses_and_bounds()
|
|
42
|
+
# sort the guesses by the lowest cost
|
|
43
|
+
cost_func = lambda x: -self.posterior(x)
|
|
44
|
+
guesses = sorted(guesses, key=cost_func)
|
|
47
45
|
|
|
48
46
|
# minimise based on the best guess
|
|
49
|
-
|
|
47
|
+
opt_method = "Nelder-Mead"
|
|
48
|
+
self.min_result = minimize(
|
|
49
|
+
fun=cost_func, x0=guesses[0], bounds=self.bounds, method=opt_method
|
|
50
|
+
)
|
|
50
51
|
self.MAP = self.min_result.x
|
|
51
52
|
self.mode = self.MAP[0]
|
|
52
53
|
|
|
53
54
|
# if we were using a reduced sample, use full sample
|
|
54
55
|
if self.skip > 1:
|
|
55
|
-
self.
|
|
56
|
-
self.
|
|
57
|
-
|
|
56
|
+
self.fitted_samples = self.sample
|
|
57
|
+
self.min_result = minimize(
|
|
58
|
+
fun=cost_func,
|
|
59
|
+
x0=self.MAP,
|
|
60
|
+
bounds=self.bounds,
|
|
61
|
+
method=opt_method,
|
|
62
|
+
)
|
|
58
63
|
self.MAP = self.min_result.x
|
|
59
64
|
self.mode = self.MAP[0]
|
|
60
65
|
|
|
@@ -66,25 +71,34 @@ class UnimodalPdf(DensityEstimator):
|
|
|
66
71
|
self.upr_limit = x0 + s0 * (4 * exp(f) + 1)
|
|
67
72
|
self.lwr_limit = x0 - s0 * (4 * exp(-f) + 1)
|
|
68
73
|
|
|
69
|
-
def
|
|
70
|
-
mu, sigma, skew = self.sample_moments()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
def generate_guesses_and_bounds(self) -> tuple[list, list]:
|
|
75
|
+
mu, sigma, skew = self.sample_moments(self.fitted_samples)
|
|
76
|
+
lwr, upr = sample_hdi(sample=self.sample, fraction=0.5)
|
|
77
|
+
|
|
78
|
+
bounds = [
|
|
79
|
+
(lwr, upr),
|
|
80
|
+
(sigma * 0.1, sigma * 10),
|
|
81
|
+
(0.0, 5.0),
|
|
82
|
+
(-3.0, 3.0),
|
|
83
|
+
(1e-2, 20.0),
|
|
84
|
+
(1.0, 6.0),
|
|
85
|
+
]
|
|
86
|
+
x0 = [lwr * (1 - f) + upr * f for f in [0.3, 0.5, 0.7]]
|
|
74
87
|
s0 = [sigma, sigma * 2]
|
|
88
|
+
ln_v = [0.25, 2.0]
|
|
75
89
|
f = [0.5 * skew, skew]
|
|
76
90
|
k = [1.0, 4.0, 8.0]
|
|
77
91
|
q = [2.0]
|
|
78
92
|
|
|
79
|
-
return [array(i) for i in product(x0, s0,
|
|
93
|
+
return [array(i) for i in product(x0, s0, ln_v, f, k, q)], bounds
|
|
80
94
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
@staticmethod
|
|
96
|
+
def sample_moments(samples: ndarray) -> tuple[float, float, float]:
|
|
97
|
+
mu = mean(samples)
|
|
98
|
+
x2 = samples**2
|
|
99
|
+
x3 = x2 * samples
|
|
85
100
|
sig = sqrt(mean(x2) - mu**2)
|
|
86
101
|
skew = (mean(x3) - 3 * mu * sig**2 - mu**3) / sig**3
|
|
87
|
-
|
|
88
102
|
return mu, sig, skew
|
|
89
103
|
|
|
90
104
|
def __call__(self, x: ndarray) -> ndarray:
|
|
@@ -112,35 +126,31 @@ class UnimodalPdf(DensityEstimator):
|
|
|
112
126
|
integral = intervals.cumsum()[inverse_sort]
|
|
113
127
|
return integral if x.size > 1 else integral[0]
|
|
114
128
|
|
|
115
|
-
def
|
|
116
|
-
|
|
129
|
+
def evaluate_model(self, x: ndarray, theta: ndarray) -> ndarray:
|
|
130
|
+
return self.pdf_model(x, theta) / self.norm(theta)
|
|
117
131
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return self.log_pdf_model(self.x, paras).sum() - normalisation
|
|
122
|
-
else:
|
|
123
|
-
return -1e50
|
|
132
|
+
def posterior(self, theta: ndarray) -> float:
|
|
133
|
+
normalisation = self.fitted_samples.size * log(self.norm(theta))
|
|
134
|
+
return self.log_pdf_model(self.fitted_samples, theta).sum() - normalisation
|
|
124
135
|
|
|
125
|
-
def norm(self,
|
|
126
|
-
v = self.pdf_model(self.u, [0.0, self.sd, *
|
|
127
|
-
integral = (self.w * v).sum() *
|
|
136
|
+
def norm(self, theta: ndarray) -> float:
|
|
137
|
+
v = self.pdf_model(self.u, [0.0, self.sd, *theta[2:]])
|
|
138
|
+
integral = (self.w * v).sum() * theta[1]
|
|
128
139
|
return integral
|
|
129
140
|
|
|
130
|
-
def pdf_model(self, x,
|
|
131
|
-
return exp(self.log_pdf_model(x,
|
|
141
|
+
def pdf_model(self, x: ndarray, theta: ndarray) -> ndarray:
|
|
142
|
+
return exp(self.log_pdf_model(x, theta))
|
|
132
143
|
|
|
133
|
-
def log_pdf_model(self, x,
|
|
134
|
-
x0, s0,
|
|
135
|
-
v = exp(
|
|
144
|
+
def log_pdf_model(self, x: ndarray, theta: ndarray) -> ndarray:
|
|
145
|
+
x0, s0, ln_v, f, k, q = theta
|
|
146
|
+
v = exp(ln_v)
|
|
136
147
|
z0 = (x - x0) / s0
|
|
137
|
-
|
|
138
|
-
z = z0 / ds
|
|
148
|
+
z = z0 * exp(-f * tanh(z0 / k))
|
|
139
149
|
|
|
140
150
|
log_prob = -(0.5 * (1 + v)) * log(1 + (abs(z) ** q) / v)
|
|
141
151
|
return log_prob
|
|
142
152
|
|
|
143
|
-
def moments(self):
|
|
153
|
+
def moments(self) -> tuple[float, ...]:
|
|
144
154
|
"""
|
|
145
155
|
Calculate the mean, variance skewness and excess kurtosis of the estimated PDF.
|
|
146
156
|
|
inference/posterior.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
.. moduleauthor:: Chris Bowman <chris.bowman.physics@gmail.com>
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from numpy import ndarray
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class Posterior:
|
|
7
9
|
"""
|
|
@@ -20,7 +22,7 @@ class Posterior:
|
|
|
20
22
|
self.likelihood = likelihood
|
|
21
23
|
self.prior = prior
|
|
22
24
|
|
|
23
|
-
def __call__(self, theta):
|
|
25
|
+
def __call__(self, theta: ndarray) -> float:
|
|
24
26
|
"""
|
|
25
27
|
Returns the log-posterior probability for the given set of model parameters.
|
|
26
28
|
|
|
@@ -32,7 +34,7 @@ class Posterior:
|
|
|
32
34
|
"""
|
|
33
35
|
return self.likelihood(theta) + self.prior(theta)
|
|
34
36
|
|
|
35
|
-
def gradient(self, theta):
|
|
37
|
+
def gradient(self, theta: ndarray) -> ndarray:
|
|
36
38
|
"""
|
|
37
39
|
Returns the gradient of the log-posterior with respect to model parameters.
|
|
38
40
|
|
|
@@ -44,7 +46,7 @@ class Posterior:
|
|
|
44
46
|
"""
|
|
45
47
|
return self.likelihood.gradient(theta) + self.prior.gradient(theta)
|
|
46
48
|
|
|
47
|
-
def cost(self, theta):
|
|
49
|
+
def cost(self, theta: ndarray) -> float:
|
|
48
50
|
"""
|
|
49
51
|
Returns the 'cost', defined as the negative log-posterior probability, for the
|
|
50
52
|
given set of model parameters. Minimising the value of the cost therefore
|
|
@@ -58,7 +60,7 @@ class Posterior:
|
|
|
58
60
|
"""
|
|
59
61
|
return -(self.likelihood(theta) + self.prior(theta))
|
|
60
62
|
|
|
61
|
-
def cost_gradient(self, theta):
|
|
63
|
+
def cost_gradient(self, theta: ndarray) -> ndarray:
|
|
62
64
|
"""
|
|
63
65
|
Returns the gradient of the negative log-posterior with respect to model parameters.
|
|
64
66
|
|