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.
@@ -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 quadtrature weights and axes
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.generate_guesses()
43
-
44
- # sort the guesses by the lowest score
45
- minfunc = lambda x: -self.posterior(x)
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
- self.min_result = minimize(minfunc, guesses[0], method="Nelder-Mead")
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.x = self.sample
56
- self.n = self.n_samps
57
- self.min_result = minimize(minfunc, self.MAP, method="Nelder-Mead")
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 generate_guesses(self):
70
- mu, sigma, skew = self.sample_moments()
71
-
72
- x0 = [mu, mu - sigma * skew * 0.15, mu - sigma * skew * 0.3]
73
- v = [0, 5.0]
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, v, f, k, q)]
93
+ return [array(i) for i in product(x0, s0, ln_v, f, k, q)], bounds
80
94
 
81
- def sample_moments(self):
82
- mu = mean(self.x)
83
- x2 = self.x**2
84
- x3 = x2 * self.x
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 posterior(self, paras):
116
- x0, s0, v, f, k, q = paras
129
+ def evaluate_model(self, x: ndarray, theta: ndarray) -> ndarray:
130
+ return self.pdf_model(x, theta) / self.norm(theta)
117
131
 
118
- # prior checks
119
- if (s0 > 0) & (0 < k < 20) & (1 < q < 6):
120
- normalisation = self.n * log(self.norm(paras))
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, pvec):
126
- v = self.pdf_model(self.u, [0.0, self.sd, *pvec[2:]])
127
- integral = (self.w * v).sum() * pvec[1]
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, pvec):
131
- return exp(self.log_pdf_model(x, pvec))
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, pvec):
134
- x0, s0, v, f, k, q = pvec
135
- v = exp(v) + 1
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
- ds = exp(f * tanh(z0 / k))
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