inference-tools 0.13.3__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 +16 -16
- 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.3.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.3.dist-info → inference_tools-0.13.4.dist-info}/WHEEL +1 -1
- inference_tools-0.13.3.dist-info/RECORD +0 -33
- {inference_tools-0.13.3.dist-info → inference_tools-0.13.4.dist-info}/LICENSE +0 -0
- {inference_tools-0.13.3.dist-info → inference_tools-0.13.4.dist-info}/top_level.txt +0 -0
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
|
|