binference 0.1.0__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.
- binference/__init__.py +85 -0
- binference/_kernels.py +252 -0
- binference/_quadrature.py +202 -0
- binference/_simplex.py +54 -0
- binference/_utils.py +139 -0
- binference/_version.py +1 -0
- binference/basis.py +314 -0
- binference/bins.py +645 -0
- binference/density.py +323 -0
- binference/diagnostics.py +400 -0
- binference/estimator.py +197 -0
- binference/inference.py +368 -0
- binference/io.py +309 -0
- binference/likelihoods.py +1684 -0
- binference/measure.py +246 -0
- binference/model.py +833 -0
- binference/plotting.py +538 -0
- binference/priors/__init__.py +47 -0
- binference/priors/base.py +248 -0
- binference/priors/dirichlet.py +640 -0
- binference/priors/hyper.py +240 -0
- binference/priors/logistic.py +676 -0
- binference/priors/rates.py +139 -0
- binference/response.py +539 -0
- binference/results.py +2271 -0
- binference/samplers/__init__.py +19 -0
- binference/samplers/_curvature.py +67 -0
- binference/samplers/_nuts_core.py +284 -0
- binference/samplers/base.py +118 -0
- binference/samplers/bootstrap.py +136 -0
- binference/samplers/gibbs.py +287 -0
- binference/samplers/laplace.py +200 -0
- binference/samplers/nuts.py +681 -0
- binference/samplers/optimize.py +488 -0
- binference/samplers/vb.py +118 -0
- binference/selection.py +621 -0
- binference/simulate.py +720 -0
- binference/validation.py +477 -0
- binference/warnings.py +48 -0
- binference-0.1.0.dist-info/METADATA +130 -0
- binference-0.1.0.dist-info/RECORD +43 -0
- binference-0.1.0.dist-info/WHEEL +4 -0
- binference-0.1.0.dist-info/licenses/LICENSE +21 -0
binference/measure.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Measures: the coordinate in which a population is flat within each bin.
|
|
2
|
+
|
|
3
|
+
A measure is a strictly increasing map :math:`u = T(z)` of a bin axis. Within a
|
|
4
|
+
bin the population is taken to be flat in :math:`u`, so its density in
|
|
5
|
+
:math:`z` is proportional to :math:`T'(z)`. Linear bins use :math:`u = z`;
|
|
6
|
+
logarithmic bins use :math:`u = \\log z`.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
from scipy.integrate import cumulative_trapezoid
|
|
16
|
+
|
|
17
|
+
from ._utils import frozen
|
|
18
|
+
|
|
19
|
+
__all__ = ["Measure"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Measure:
|
|
23
|
+
"""A strictly increasing map ``u = T(z)`` of one bin axis.
|
|
24
|
+
|
|
25
|
+
Build one with :meth:`linear`, :meth:`log`, :meth:`transform` (a function
|
|
26
|
+
and its inverse), :meth:`density` (a positive density ``g(z)``, whose
|
|
27
|
+
cumulative integral is the map) or :meth:`tabulated`. Strings ``"linear"``
|
|
28
|
+
and ``"log"`` are accepted wherever a measure is.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__slots__ = ("_forward", "_hash", "_inverse", "_key", "_table", "name")
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
name: str,
|
|
36
|
+
forward: Callable[[np.ndarray], np.ndarray],
|
|
37
|
+
inverse: Callable[[np.ndarray], np.ndarray],
|
|
38
|
+
key: tuple,
|
|
39
|
+
table: tuple[np.ndarray, np.ndarray] | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
self.name = name
|
|
42
|
+
self._forward = forward
|
|
43
|
+
self._inverse = inverse
|
|
44
|
+
# identity: ("named", name), ("table", z bytes, u bytes) or ("fn", forward, inverse)
|
|
45
|
+
self._key = key
|
|
46
|
+
self._hash = hash(key)
|
|
47
|
+
self._table = table
|
|
48
|
+
|
|
49
|
+
# ------------------------------------------------------------ constructors
|
|
50
|
+
@classmethod
|
|
51
|
+
def linear(cls) -> Measure:
|
|
52
|
+
"""``u = z``: flat within bins in the bin coordinate itself."""
|
|
53
|
+
return _NAMED["linear"]
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def log(cls) -> Measure:
|
|
57
|
+
"""``u = log z`` (``z > 0``): flat within bins in the logarithm."""
|
|
58
|
+
return _NAMED["log"]
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def transform(
|
|
62
|
+
cls,
|
|
63
|
+
forward: Callable[[np.ndarray], np.ndarray],
|
|
64
|
+
inverse: Callable[[np.ndarray], np.ndarray],
|
|
65
|
+
name: str = "transform",
|
|
66
|
+
) -> Measure:
|
|
67
|
+
"""A user map ``u = forward(z)`` with its inverse; both take and return arrays.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
forward, inverse : callable
|
|
72
|
+
The map ``z -> u`` and its inverse, strictly increasing.
|
|
73
|
+
name : str, optional
|
|
74
|
+
Name shown for the measure.
|
|
75
|
+
"""
|
|
76
|
+
if not (callable(forward) and callable(inverse)):
|
|
77
|
+
raise TypeError("forward and inverse must be callables.")
|
|
78
|
+
return cls(name, forward, inverse, ("fn", forward, inverse))
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def density(
|
|
82
|
+
cls, g: Callable[[np.ndarray], np.ndarray], lo: float, hi: float, n: int = 4097
|
|
83
|
+
) -> Measure:
|
|
84
|
+
"""The map whose derivative is the positive density ``g`` on ``[lo, hi]``.
|
|
85
|
+
|
|
86
|
+
``T(z) = integral of g from lo to z``, computed by the trapezoidal rule
|
|
87
|
+
on ``n`` points and interpolated linearly, as is its inverse.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
g : callable
|
|
92
|
+
Positive density, evaluated on an array of ``z``.
|
|
93
|
+
lo, hi : float
|
|
94
|
+
Range of ``z`` the map covers.
|
|
95
|
+
n : int, optional
|
|
96
|
+
Points of the trapezoidal rule that tabulates the map.
|
|
97
|
+
"""
|
|
98
|
+
z = np.linspace(float(lo), float(hi), int(n))
|
|
99
|
+
dens = np.asarray(g(z), dtype=float)
|
|
100
|
+
if dens.shape != z.shape or not np.all(np.isfinite(dens)) or np.any(dens <= 0):
|
|
101
|
+
raise ValueError("g must return finite, strictly positive values on [lo, hi].")
|
|
102
|
+
return cls.tabulated(z, cumulative_trapezoid(dens, z, initial=0.0), name="density")
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def tabulated(cls, z: Any, u: Any, name: str = "tabulated") -> Measure:
|
|
106
|
+
"""A piecewise-linear map through the points ``(z, u)``, both strictly increasing.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
z, u : array_like
|
|
111
|
+
Points of the map, at least two.
|
|
112
|
+
name : str, optional
|
|
113
|
+
Name shown for the measure.
|
|
114
|
+
"""
|
|
115
|
+
z = np.array(z, dtype=float)
|
|
116
|
+
u = np.array(u, dtype=float)
|
|
117
|
+
if z.ndim != 1 or z.shape != u.shape or z.size < 2:
|
|
118
|
+
raise ValueError("z and u must be 1-D arrays of the same length (at least 2).")
|
|
119
|
+
if not (np.all(np.diff(z) > 0) and np.all(np.diff(u) > 0)):
|
|
120
|
+
raise ValueError("z and u must both be strictly increasing.")
|
|
121
|
+
z, u = frozen(z), frozen(u)
|
|
122
|
+
return cls(
|
|
123
|
+
name,
|
|
124
|
+
lambda x: np.interp(x, z, u),
|
|
125
|
+
lambda y: np.interp(y, u, z),
|
|
126
|
+
("table", z.tobytes(), u.tobytes()),
|
|
127
|
+
table=(z, u),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def coerce(cls, measure: Measure | str) -> Measure:
|
|
132
|
+
"""A :class:`Measure` from a measure or one of the names ``"linear"`` and ``"log"``.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
measure : Measure or {"linear", "log"}
|
|
137
|
+
A measure or its name.
|
|
138
|
+
"""
|
|
139
|
+
if isinstance(measure, Measure):
|
|
140
|
+
return measure
|
|
141
|
+
if isinstance(measure, str) and measure in _NAMED:
|
|
142
|
+
return _NAMED[measure]
|
|
143
|
+
raise ValueError(f"measure must be a Measure, 'linear' or 'log'; got {measure!r}.")
|
|
144
|
+
|
|
145
|
+
# ----------------------------------------------------------------- methods
|
|
146
|
+
@property
|
|
147
|
+
def is_named(self) -> bool:
|
|
148
|
+
"""Whether this is one of the built-in measures ``"linear"`` and ``"log"``."""
|
|
149
|
+
return self._key[0] == "named"
|
|
150
|
+
|
|
151
|
+
def forward(self, z: Any) -> np.ndarray:
|
|
152
|
+
"""``u = T(z)``.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
z : array_like
|
|
157
|
+
Values of the bin coordinate.
|
|
158
|
+
"""
|
|
159
|
+
return np.asarray(self._forward(np.asarray(z, dtype=float)), dtype=float)
|
|
160
|
+
|
|
161
|
+
def inverse(self, u: Any) -> np.ndarray:
|
|
162
|
+
"""``z = T^{-1}(u)``.
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
u : array_like
|
|
167
|
+
Values of the measure coordinate.
|
|
168
|
+
"""
|
|
169
|
+
return np.asarray(self._inverse(np.asarray(u, dtype=float)), dtype=float)
|
|
170
|
+
|
|
171
|
+
def log_jacobian(self, z: Any) -> np.ndarray:
|
|
172
|
+
"""``log dT/dz``: the log of the within-bin population density in ``z``, up to a constant.
|
|
173
|
+
|
|
174
|
+
Exact for linear, log and tabulated measures; a central difference for
|
|
175
|
+
:meth:`transform`.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
z : array_like
|
|
180
|
+
Values of the bin coordinate.
|
|
181
|
+
"""
|
|
182
|
+
z = np.asarray(z, dtype=float)
|
|
183
|
+
if self is _NAMED["linear"]:
|
|
184
|
+
return np.zeros_like(z)
|
|
185
|
+
if self is _NAMED["log"]:
|
|
186
|
+
return -np.log(z)
|
|
187
|
+
if self._table is not None:
|
|
188
|
+
zt, ut = self._table
|
|
189
|
+
j = np.clip(np.searchsorted(zt, z, side="right") - 1, 0, zt.size - 2)
|
|
190
|
+
return np.log((ut[j + 1] - ut[j]) / (zt[j + 1] - zt[j]))
|
|
191
|
+
h = 1e-6 * np.where(z != 0, np.abs(z), 1.0) # relative, so small z stay in the domain
|
|
192
|
+
return np.log((self.forward(z + h) - self.forward(z - h)) / (2 * h))
|
|
193
|
+
|
|
194
|
+
def _covers(self, lo: float, hi: float) -> bool:
|
|
195
|
+
# whether [lo, hi] lies inside the measure's domain
|
|
196
|
+
if self is _NAMED["log"]:
|
|
197
|
+
return lo > 0
|
|
198
|
+
if self._table is not None:
|
|
199
|
+
return self._table[0][0] <= lo and hi <= self._table[0][-1]
|
|
200
|
+
return True
|
|
201
|
+
|
|
202
|
+
def _tabulate(self, edges: np.ndarray, n: int = 4097) -> tuple[np.ndarray, np.ndarray]:
|
|
203
|
+
# points (z, u) that reproduce the map on [edges[0], edges[-1]]
|
|
204
|
+
if self._table is not None:
|
|
205
|
+
return self._table
|
|
206
|
+
z = np.unique(np.concatenate([np.linspace(edges[0], edges[-1], n), edges]))
|
|
207
|
+
return z, self.forward(z)
|
|
208
|
+
|
|
209
|
+
def _check(self, edges: np.ndarray, axis: int) -> np.ndarray:
|
|
210
|
+
# the edges mapped to u; raises unless they stay finite and increasing
|
|
211
|
+
if not self._covers(edges[0], edges[-1]):
|
|
212
|
+
raise ValueError(f"Edges along axis {axis} lie outside the domain of {self!r}.")
|
|
213
|
+
u = self.forward(edges)
|
|
214
|
+
if not (np.all(np.isfinite(u)) and np.all(np.diff(u) > 0)):
|
|
215
|
+
raise ValueError(f"The measure must map the edges of axis {axis} to increasing u.")
|
|
216
|
+
return u
|
|
217
|
+
|
|
218
|
+
def __eq__(self, other: object) -> bool:
|
|
219
|
+
if not isinstance(other, Measure):
|
|
220
|
+
return NotImplemented
|
|
221
|
+
return self._key == other._key
|
|
222
|
+
|
|
223
|
+
def __hash__(self) -> int:
|
|
224
|
+
return self._hash
|
|
225
|
+
|
|
226
|
+
def __reduce__(self):
|
|
227
|
+
kind = self._key[0]
|
|
228
|
+
if kind == "named":
|
|
229
|
+
return (Measure.coerce, (self.name,))
|
|
230
|
+
if kind == "table":
|
|
231
|
+
z, u = (np.array(t) for t in self._table)
|
|
232
|
+
return (Measure.tabulated, (z, u, self.name))
|
|
233
|
+
return (Measure.transform, (self._forward, self._inverse, self.name))
|
|
234
|
+
|
|
235
|
+
def __repr__(self) -> str:
|
|
236
|
+
return f"Measure.{self.name}()" if self.is_named else f"Measure({self.name!r})"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _identity(x: np.ndarray) -> np.ndarray:
|
|
240
|
+
return x
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
_NAMED = {
|
|
244
|
+
"linear": Measure("linear", _identity, _identity, ("named", "linear")),
|
|
245
|
+
"log": Measure("log", np.log, np.exp, ("named", "log")),
|
|
246
|
+
}
|