inference-tools 0.14.0__py3-none-any.whl → 0.14.1__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 CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.14.0'
21
- __version_tuple__ = version_tuple = (0, 14, 0)
20
+ __version__ = version = '0.14.1'
21
+ __version_tuple__ = version_tuple = (0, 14, 1)
inference/likelihoods.py CHANGED
@@ -3,7 +3,7 @@
3
3
  """
4
4
 
5
5
  from abc import ABC, abstractmethod
6
- from numpy import array, log, exp, pi, sqrt, ndarray
6
+ from numpy import array, log, exp, pi, sqrt, ndarray, logaddexp
7
7
 
8
8
 
9
9
  class Likelihood(ABC):
@@ -254,7 +254,7 @@ class LogisticLikelihood(Likelihood):
254
254
 
255
255
  def _log_likelihood(self, predictions: ndarray) -> float:
256
256
  z = (self.y - predictions) * self.inv_scale
257
- return z.sum() - 2 * log(1 + exp(z)).sum() + self.normalisation
257
+ return z.sum() - 2 * logaddexp(0.0, z).sum() + self.normalisation
258
258
 
259
259
  def _log_likelihood_gradient(
260
260
  self, predictions: ndarray, predictions_jacobian: ndarray
@@ -1,13 +1,14 @@
1
- from copy import copy
2
1
  import matplotlib.pyplot as plt
3
2
 
4
3
  from numpy import ndarray, float64
5
4
  from numpy import array, savez, savez_compressed, load, zeros
6
- from numpy import sqrt, var, isfinite, exp, log, dot, mean, argmax, percentile
5
+ from numpy import var, isfinite, exp, mean, argmax, percentile, cov
7
6
  from numpy.random import default_rng
8
7
 
9
8
  from inference.mcmc.utilities import Bounds, ChainProgressPrinter, effective_sample_size
10
9
  from inference.mcmc.base import MarkovChain
10
+ from inference.mcmc.hmc.epsilon import EpsilonSelector
11
+ from inference.mcmc.hmc.mass import get_particle_mass
11
12
 
12
13
 
13
14
  class HamiltonianChain(MarkovChain):
@@ -51,10 +52,12 @@ class HamiltonianChain(MarkovChain):
51
52
  in the form ``(lower_bounds, upper_bounds)``.
52
53
 
53
54
  :param inverse_mass: \
54
- A vector specifying the inverse-mass value to be used for each parameter. The
55
- inverse-mass is used to transform the momentum distribution in order to make
56
- the problem more isotropic. Ideally, the inverse-mass for each parameter should
57
- be set to the variance of the marginal distribution of that parameter.
55
+ The inverse-mass can be given as either a vector or matrix, and is used to
56
+ transform the momentum distribution so the chain can explore the posterior
57
+ efficiently. If given as a vector, the inverse mass should have values which
58
+ approximate the variance of the marginal distributions of each parameter.
59
+ If given as a matrix, the inverse mass should be a valid covariance matrix
60
+ for a multivariate normal distribution which approximates the posterior.
58
61
 
59
62
  :param bool display_progress: \
60
63
  If set as ``True``, a message is displayed during sampling
@@ -77,9 +80,6 @@ class HamiltonianChain(MarkovChain):
77
80
  # if no gradient function is supplied, default to finite difference
78
81
  self.grad = self.finite_diff if grad is None else grad
79
82
 
80
- # set the inverse mass to 1 if none supplied
81
- self.inv_mass = 1.0 if inverse_mass is None else inverse_mass
82
- self.sqrt_mass = 1.0 / sqrt(self.inv_mass)
83
83
  self.temperature = temperature
84
84
  self.inv_temp = 1.0 / temperature
85
85
 
@@ -91,7 +91,12 @@ class HamiltonianChain(MarkovChain):
91
91
  self.theta = [start]
92
92
  self.probs = [self.posterior(start) * self.inv_temp]
93
93
  self.leapfrog_steps = [0]
94
- self.n_parameters = len(start)
94
+ self.n_parameters = start.size
95
+ self.mass = get_particle_mass(
96
+ inverse_mass=inverse_mass if inverse_mass is not None else 1.0,
97
+ n_parameters=self.n_parameters,
98
+ )
99
+
95
100
  self.chain_length = 1
96
101
 
97
102
  # set either the bounded or unbounded leapfrog update
@@ -125,16 +130,16 @@ class HamiltonianChain(MarkovChain):
125
130
  """
126
131
  steps_taken = 0
127
132
  for attempt in range(self.max_attempts):
128
- r0 = self.rng.normal(size=self.n_parameters, scale=self.sqrt_mass)
133
+ r0 = self.mass.sample_momentum(self.rng)
129
134
  t0 = self.theta[-1]
130
- H0 = 0.5 * dot(r0, r0 * self.inv_mass) - self.probs[-1]
135
+ H0 = self.kinetic_energy(r0) - self.probs[-1]
131
136
 
132
137
  n_steps = int(self.steps * (1 + (self.rng.random() - 0.5) * 0.2))
133
138
  t, r = self.run_leapfrog(t0.copy(), r0.copy(), n_steps)
134
139
 
135
140
  steps_taken += n_steps
136
141
  p = self.posterior(t) * self.inv_temp
137
- H = 0.5 * dot(r, r * self.inv_mass) - p
142
+ H = self.kinetic_energy(r) - p
138
143
  accept_prob = exp(H0 - H)
139
144
 
140
145
  self.ES.add_probability(
@@ -159,38 +164,49 @@ class HamiltonianChain(MarkovChain):
159
164
  def standard_leapfrog(
160
165
  self, t: ndarray, r: ndarray, n_steps: int
161
166
  ) -> tuple[ndarray, ndarray]:
162
- t_step = self.inv_mass * self.ES.epsilon
163
167
  r_step = self.inv_temp * self.ES.epsilon
164
168
  r += (0.5 * r_step) * self.grad(t)
169
+
165
170
  for _ in range(n_steps - 1):
166
- t += t_step * r
171
+ t += self.ES.epsilon * self.mass.get_velocity(r)
167
172
  r += r_step * self.grad(t)
168
- t += t_step * r
173
+
174
+ t += self.ES.epsilon * self.mass.get_velocity(r)
169
175
  r += (0.5 * r_step) * self.grad(t)
170
176
  return t, r
171
177
 
172
178
  def bounded_leapfrog(
173
179
  self, t: ndarray, r: ndarray, n_steps: int
174
180
  ) -> tuple[ndarray, ndarray]:
175
- t_step = self.inv_mass * self.ES.epsilon
176
181
  r_step = self.inv_temp * self.ES.epsilon
177
182
  r += (0.5 * r_step) * self.grad(t)
183
+
178
184
  for _ in range(n_steps - 1):
179
- t += t_step * r
185
+ t += self.ES.epsilon * self.mass.get_velocity(r)
180
186
  t, reflections = self.bounds.reflect_momenta(t)
181
187
  r *= reflections
182
188
  r += r_step * self.grad(t)
183
- t += t_step * r
189
+
190
+ t += self.ES.epsilon * self.mass.get_velocity(r)
184
191
  t, reflections = self.bounds.reflect_momenta(t)
185
192
  r *= reflections
186
193
  r += (0.5 * r_step) * self.grad(t)
187
194
  return t, r
188
195
 
189
196
  def hamiltonian(self, t: ndarray, r: ndarray) -> float:
190
- return 0.5 * dot(r, r * self.inv_mass) - self.posterior(t) * self.inv_temp
197
+ return 0.5 * (r @ self.mass.get_velocity(r)) - self.posterior(t) * self.inv_temp
198
+
199
+ def kinetic_energy(self, r: ndarray) -> float:
200
+ return 0.5 * (r @ self.mass.get_velocity(r))
191
201
 
192
- def estimate_mass(self, burn=1, thin=1):
193
- self.inv_mass = var(array(self.theta[burn::thin]), axis=0)
202
+ def estimate_mass(self, burn=1, thin=1, diagonal=True):
203
+ if diagonal:
204
+ inverse_mass = var(array(self.theta[burn::thin]), axis=0)
205
+ else:
206
+ inverse_mass = cov(array(self.theta[burn::thin]).T)
207
+ self.mass = get_particle_mass(
208
+ inverse_mass=inverse_mass, n_parameters=self.n_parameters
209
+ )
194
210
 
195
211
  def finite_diff(self, t: ndarray) -> ndarray:
196
212
  p = self.posterior(t) * self.inv_temp
@@ -393,7 +409,7 @@ class HamiltonianChain(MarkovChain):
393
409
 
394
410
  def save(self, filename, compressed=False):
395
411
  items = {
396
- "inv_mass": self.inv_mass,
412
+ "inv_mass": self.mass.inv_mass,
397
413
  "inv_temp": self.inv_temp,
398
414
  "theta": self.theta,
399
415
  "probs": self.probs,
@@ -451,69 +467,3 @@ class HamiltonianChain(MarkovChain):
451
467
  # build the epsilon selector
452
468
  chain.ES.load_items(D)
453
469
  return chain
454
-
455
-
456
- class EpsilonSelector:
457
- def __init__(self, epsilon: float):
458
- # storage
459
- self.epsilon = epsilon
460
- self.epsilon_values = [copy(epsilon)] # sigma values after each assessment
461
- self.epsilon_checks = [0.0] # chain locations at which sigma was assessed
462
-
463
- # tracking variables
464
- self.avg = 0
465
- self.var = 0
466
- self.num = 0
467
-
468
- # settings for epsilon adjustment algorithm
469
- self.accept_rate = 0.65
470
- self.chk_int = 15 # interval of steps at which proposal widths are adjusted
471
- self.growth_factor = 1.4 # growth factor for self.chk_int
472
-
473
- def add_probability(self, p: float):
474
- self.num += 1
475
- self.avg += p
476
- self.var += max(p * (1 - p), 0.03)
477
-
478
- if self.num >= self.chk_int:
479
- self.update_epsilon()
480
-
481
- def update_epsilon(self):
482
- """
483
- looks at average tries over recent steps, and adjusts proposal
484
- widths self.sigma to bring the average towards self.target_tries.
485
- """
486
- # normal approximation of poisson binomial distribution
487
- mu = self.avg / self.num
488
- std = sqrt(self.var) / self.num
489
-
490
- # now check if the desired success rate is within 2-sigma
491
- if ~(mu - 2 * std < self.accept_rate < mu + 2 * std):
492
- adj = (log(self.accept_rate) / log(mu)) ** 0.15
493
- adj = min(adj, 2.0)
494
- adj = max(adj, 0.5)
495
- self.adjust_epsilon(adj)
496
- else: # increase the check interval
497
- self.chk_int = int((self.growth_factor * self.chk_int) * 0.1) * 10
498
-
499
- def adjust_epsilon(self, ratio: float):
500
- self.epsilon *= ratio
501
- self.epsilon_values.append(copy(self.epsilon))
502
- self.epsilon_checks.append(self.epsilon_checks[-1] + self.num)
503
- self.avg = 0
504
- self.var = 0
505
- self.num = 0
506
-
507
- def get_items(self):
508
- return self.__dict__
509
-
510
- def load_items(self, dictionary: dict):
511
- self.epsilon = float(dictionary["epsilon"])
512
- self.epsilon_values = list(dictionary["epsilon_values"])
513
- self.epsilon_checks = list(dictionary["epsilon_checks"])
514
- self.avg = float(dictionary["avg"])
515
- self.var = float(dictionary["var"])
516
- self.num = float(dictionary["num"])
517
- self.accept_rate = float(dictionary["accept_rate"])
518
- self.chk_int = int(dictionary["chk_int"])
519
- self.growth_factor = float(dictionary["growth_factor"])
@@ -0,0 +1,68 @@
1
+ from copy import copy
2
+ from numpy import sqrt, log
3
+
4
+
5
+ class EpsilonSelector:
6
+ def __init__(self, epsilon: float):
7
+ # storage
8
+ self.epsilon = epsilon
9
+ self.epsilon_values = [copy(epsilon)] # sigma values after each assessment
10
+ self.epsilon_checks = [0.0] # chain locations at which sigma was assessed
11
+
12
+ # tracking variables
13
+ self.avg = 0
14
+ self.var = 0
15
+ self.num = 0
16
+
17
+ # settings for epsilon adjustment algorithm
18
+ self.accept_rate = 0.65
19
+ self.chk_int = 15 # interval of steps at which proposal widths are adjusted
20
+ self.growth_factor = 1.4 # growth factor for self.chk_int
21
+
22
+ def add_probability(self, p: float):
23
+ self.num += 1
24
+ self.avg += p
25
+ self.var += max(p * (1 - p), 0.03)
26
+
27
+ if self.num >= self.chk_int:
28
+ self.update_epsilon()
29
+
30
+ def update_epsilon(self):
31
+ """
32
+ looks at the acceptance rate of proposed steps and adjusts the epsilon
33
+ value to bring the acceptance rate toward its target value.
34
+ """
35
+ # normal approximation of poisson binomial distribution
36
+ mu = self.avg / self.num
37
+ std = sqrt(self.var) / self.num
38
+
39
+ # now check if the desired success rate is within 2-sigma
40
+ if ~(mu - 2 * std < self.accept_rate < mu + 2 * std):
41
+ adj = (log(self.accept_rate) / log(mu)) ** 0.15
42
+ adj = min(adj, 2.0)
43
+ adj = max(adj, 0.5)
44
+ self.adjust_epsilon(adj)
45
+ else: # increase the check interval
46
+ self.chk_int = int((self.growth_factor * self.chk_int) * 0.1) * 10
47
+
48
+ def adjust_epsilon(self, ratio: float):
49
+ self.epsilon *= ratio
50
+ self.epsilon_values.append(copy(self.epsilon))
51
+ self.epsilon_checks.append(self.epsilon_checks[-1] + self.num)
52
+ self.avg = 0
53
+ self.var = 0
54
+ self.num = 0
55
+
56
+ def get_items(self):
57
+ return self.__dict__
58
+
59
+ def load_items(self, dictionary: dict):
60
+ self.epsilon = float(dictionary["epsilon"])
61
+ self.epsilon_values = list(dictionary["epsilon_values"])
62
+ self.epsilon_checks = list(dictionary["epsilon_checks"])
63
+ self.avg = float(dictionary["avg"])
64
+ self.var = float(dictionary["var"])
65
+ self.num = float(dictionary["num"])
66
+ self.accept_rate = float(dictionary["accept_rate"])
67
+ self.chk_int = int(dictionary["chk_int"])
68
+ self.growth_factor = float(dictionary["growth_factor"])
@@ -0,0 +1,80 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Union
3
+ from numpy import ndarray, sqrt, eye, isscalar
4
+ from numpy.random import Generator
5
+ from numpy.linalg import cholesky
6
+ from scipy.linalg import solve_triangular
7
+
8
+
9
+ class ParticleMass(ABC):
10
+ inv_mass: Union[float, ndarray]
11
+
12
+ @abstractmethod
13
+ def get_velocity(self, r: ndarray) -> ndarray:
14
+ pass
15
+
16
+ @abstractmethod
17
+ def sample_momentum(self, rng: Generator) -> ndarray:
18
+ pass
19
+
20
+
21
+ class ScalarMass(ParticleMass):
22
+ def __init__(self, inv_mass: float, n_parameters: int):
23
+ self.inv_mass = inv_mass
24
+ self.sqrt_mass = 1 / sqrt(self.inv_mass)
25
+ self.n_parameters = n_parameters
26
+
27
+ def get_velocity(self, r: ndarray) -> ndarray:
28
+ return r * self.inv_mass
29
+
30
+ def sample_momentum(self, rng: Generator) -> ndarray:
31
+ return rng.normal(size=self.n_parameters, scale=self.sqrt_mass)
32
+
33
+
34
+ class VectorMass(ScalarMass):
35
+ def __init__(self, inv_mass: ndarray, n_parameters: int):
36
+ super().__init__(inv_mass, n_parameters)
37
+ assert inv_mass.ndim == 1
38
+ assert inv_mass.size == n_parameters
39
+
40
+
41
+ class MatrixMass(ParticleMass):
42
+ def __init__(self, inv_mass: ndarray, n_parameters: int):
43
+ assert inv_mass.ndim == 2
44
+ assert inv_mass.shape[0] == inv_mass.shape[1] == n_parameters
45
+ assert (inv_mass == inv_mass.T).all()
46
+
47
+ self.inv_mass = inv_mass
48
+ self.n_parameters = n_parameters
49
+ # find the cholesky decomp of the mass matrix
50
+ iL = cholesky(inv_mass)
51
+ self.L = solve_triangular(iL, eye(self.n_parameters), lower=True).T
52
+
53
+ def get_velocity(self, r: ndarray) -> ndarray:
54
+ return self.inv_mass @ r
55
+
56
+ def sample_momentum(self, rng: Generator) -> ndarray:
57
+ return self.L @ rng.normal(size=self.n_parameters)
58
+
59
+
60
+ def get_particle_mass(
61
+ inverse_mass: Union[float, ndarray], n_parameters: int
62
+ ) -> ParticleMass:
63
+ if isscalar(inverse_mass):
64
+ return ScalarMass(inverse_mass, n_parameters)
65
+
66
+ if not isinstance(inverse_mass, ndarray):
67
+ raise TypeError(
68
+ f"""\n
69
+ \r[ HamiltonianChain error ]
70
+ \r>> The value given to the 'inverse_mass' keyword argument must be either
71
+ \r>> a scalar type (e.g. int or float), or a numpy.ndarray.
72
+ \r>> Instead, the given value has type:
73
+ \r>> {type(inverse_mass)}
74
+ """
75
+ )
76
+
77
+ if inverse_mass.ndim == 1:
78
+ return VectorMass(inverse_mass, n_parameters)
79
+ else:
80
+ return MatrixMass(inverse_mass, n_parameters)
inference/plotting.py CHANGED
@@ -445,29 +445,11 @@ def hdi_plot(
445
445
  if axis is None:
446
446
  _, axis = plt.subplots()
447
447
 
448
- from numpy import take_along_axis, expand_dims
449
-
450
448
  # iterate over the intervals and plot each
451
449
  for frac, col in zip(intervals, colors):
452
- L = int(frac * n)
453
-
454
- # check that we have enough samples to estimate the HDI for the chosen fraction
455
- if n > L:
456
- # find the optimal single HDI
457
- widths = s[L:, :] - s[: n - L, :]
458
- i = expand_dims(widths.argmin(axis=0), axis=0)
459
- lwr = take_along_axis(s, i, 0).squeeze()
460
- upr = take_along_axis(s, i + L, 0).squeeze()
461
- else:
462
- lwr = s[0, :]
463
- upr = s[-1, :]
464
-
465
- if label_intervals:
466
- axis.fill_between(
467
- x, lwr, upr, color=col, label="{}% HDI".format(int(100 * frac))
468
- )
469
- else:
470
- axis.fill_between(x, lwr, upr, color=col)
450
+ lwr, upr = sample_hdi(s, fraction=frac)
451
+ lab = f"{int(100 * frac)}% HDI" if label_intervals else None
452
+ axis.fill_between(x, lwr, upr, color=col, label=lab)
471
453
 
472
454
  return axis
473
455
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: inference-tools
3
- Version: 0.14.0
3
+ Version: 0.14.1
4
4
  Summary: A collection of python tools for Bayesian data analysis
5
5
  Author-email: Chris Bowman <chris.bowman.physics@gmail.com>
6
6
  License: MIT License
@@ -42,6 +42,7 @@ Requires-Dist: pytest-cov>=3.0.0; extra == "tests"
42
42
  Requires-Dist: pyqt5>=5.15; extra == "tests"
43
43
  Requires-Dist: hypothesis>=6.24; extra == "tests"
44
44
  Requires-Dist: freezegun>=1.1.0; extra == "tests"
45
+ Dynamic: license-file
45
46
 
46
47
  # inference-tools
47
48
 
@@ -1,7 +1,7 @@
1
1
  inference/__init__.py,sha256=Wheq9bSUF5Y_jAc_w_Avi4WW2kphDK0qHGM6FsIKSxY,275
2
- inference/_version.py,sha256=CNbGkYuFNWjd3DIUYNizEoC18xKEHqri0oYO6s7QgJ8,513
3
- inference/likelihoods.py,sha256=fS_k3mRr7bv6kgDt29u_OB6emU-ARVZktf7j-eXA-2U,10008
4
- inference/plotting.py,sha256=U1M_F5I-UMtfHiaN1YihcxYq5gg_2MNyPm7MxF1LecY,19747
2
+ inference/_version.py,sha256=9-B5HerO_wiKUcm3zqJZazE8kjqwU6_WcIM1m-vWIoQ,513
3
+ inference/likelihoods.py,sha256=0mRn9S7CaX6hNv1fKVeaAFYk50bALvVbyX7E2aH3Bn8,10021
4
+ inference/plotting.py,sha256=vMpRGiZMMlVgAcVaKC2wtvjzVlBmOkC2BM90A3wSwJ8,19194
5
5
  inference/posterior.py,sha256=ptPZgzT--ehbpu57nW9GmFuyovFOSmw56HWfuC-8GGA,3584
6
6
  inference/priors.py,sha256=zDuIgJTZrqEqkp8rE-aBRlAuqBacR9aC_QNm8jNIYl8,19368
7
7
  inference/approx/__init__.py,sha256=b8xCdshVeGHyao6-P0038QB71WOMLrcYXCOYiYjK7Tk,132
@@ -17,17 +17,19 @@ inference/mcmc/__init__.py,sha256=IsEhVSIpZCDNIqgSq_21M6DH6x8F1jJbYWM0e3S3QG4,44
17
17
  inference/mcmc/base.py,sha256=cEh1LPmKd6JMop8EcuH3dvAeJYei88pcPTw1xe7tGKY,10496
18
18
  inference/mcmc/ensemble.py,sha256=JRXu7SBYXN4Y9RzgA6kGUHpZNw4q4A9wf0KOAQdlz0E,15585
19
19
  inference/mcmc/gibbs.py,sha256=f-eccDBILfaZercZii3vuJ29V505VUsCHoxhD9gZ7xA,24288
20
- inference/mcmc/hmc.py,sha256=7SDjiwzVCqme1g8v65XldQpW5dnt7O3p1IG2AYGBb4o,19484
21
20
  inference/mcmc/parallel.py,sha256=SKLzMP4aqIj1xsxKuByA1lr1GdgIu5pPzVw7hlfXZEQ,14053
22
21
  inference/mcmc/pca.py,sha256=NxC81NghGlBQslFVOk2HzpsnCjlEdDnv_w8es4Qe7PU,10695
23
22
  inference/mcmc/utilities.py,sha256=YjpK3FvV0Q98jLusrZrvGck-bjm6uZZ1U7HHH3aly8g,6048
23
+ inference/mcmc/hmc/__init__.py,sha256=R2ZjKca1CjWwzAHRKetZOAbiJKo2YZVmX4jrz2EAyL4,17661
24
+ inference/mcmc/hmc/epsilon.py,sha256=t2kNi10MSVFXjmAx5zRUARDuPu_yWbwoK2McMuaaAUs,2467
25
+ inference/mcmc/hmc/mass.py,sha256=qnxsbkogZFeqGbssZ2w4tsaUGytEXL-I0Gqs4UZzcAg,2545
24
26
  inference/pdf/__init__.py,sha256=gVmQ1HLTab6_oWMQN26A1r7PkqbApaJmBK-c7TIFxjY,270
25
27
  inference/pdf/base.py,sha256=Zj5mfFmDqTe5cFz0biBxcvEaxdOUC-SsOUjebUEX7HM,5442
26
28
  inference/pdf/hdi.py,sha256=soFw3fKQdzxbGNhU9BvFHdt0uGKfhus3E3vM6L47yhY,4638
27
29
  inference/pdf/kde.py,sha256=KSl8y---602MlxoSVH8VknNQYZ2KAOTky50QU3jRw28,12999
28
30
  inference/pdf/unimodal.py,sha256=9S05c0hq_rF-MLoDJgUmaJKRdcP8F9_Idj7Ncb6m9q0,6218
29
- inference_tools-0.14.0.dist-info/LICENSE,sha256=Y0-EfO5pdxf6d0J6Er13ZSWiPZ2o6kHvM37eRgnJdww,1069
30
- inference_tools-0.14.0.dist-info/METADATA,sha256=vlamppwmRyKBi3LuZP-IVRMsDy2UhdzoEiabRWw0aoE,5378
31
- inference_tools-0.14.0.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
32
- inference_tools-0.14.0.dist-info/top_level.txt,sha256=I7bsb71rLtH3yvVH_HSLXUosY2AwCxEG3vctNsEhbEM,10
33
- inference_tools-0.14.0.dist-info/RECORD,,
31
+ inference_tools-0.14.1.dist-info/licenses/LICENSE,sha256=Y0-EfO5pdxf6d0J6Er13ZSWiPZ2o6kHvM37eRgnJdww,1069
32
+ inference_tools-0.14.1.dist-info/METADATA,sha256=flIdPaL3VFTzogPS-k2CwjIu_kbww--4libVTB9zegY,5400
33
+ inference_tools-0.14.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
34
+ inference_tools-0.14.1.dist-info/top_level.txt,sha256=I7bsb71rLtH3yvVH_HSLXUosY2AwCxEG3vctNsEhbEM,10
35
+ inference_tools-0.14.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5