Calibration harnesses¶
Importable checks that a population fit is calibrated: simulation-based calibration for any traceable prior, simulator and fit, the per-event-prior swap test of the importance correction, and the identifiability stress test of λ₀ against Q₅₀. Tier V.
validation
¶
Calibration harnesses (implementation in plumax.population._src.validation).
SBCResult
dataclass
¶
SBCResult(ranks: Int[Array, 'sample param'], ks_pvalues: Float[Array, ' param'], param_names: tuple[str, ...], n_draws: int)
Ranks of the true parameters among their posterior draws.
Attributes:
| Name | Type | Description |
|---|---|---|
ranks |
Int[Array, 'sample param']
|
r = #{s : θ⁽ˢ⁾ < θ*}, in 0…L, per simulation and parameter. |
ks_pvalues |
Float[Array, ' param']
|
KS p-value of the ranks against uniform, per parameter. |
param_names |
tuple[str, ...]
|
The parameters, in the order of the columns. |
n_draws |
int
|
L, the posterior draws per fit. |
identifiability_stress
¶
identifiability_stress(*, lam_high_pod_low: tuple[float, float], lam_low_pod_high: tuple[float, float], key: Array, window: tuple[float, float] = (0.0, 100.0), mu: float = 0.0, sigma: float = 1.0, slope: float = 2.0, q50_std: float = 0.5, max_ground: int = 5000, num_warmup: int = 500, num_samples: int = 500) -> tuple[float, float]
Posterior corr(λ₀, Q₅₀) in a confounded and a separated regime.
With a high rate seen through a high Q₅₀, more events and a higher threshold explain the same catalog, and the correlation is strong. With a low Q₅₀ almost every event is detected, N pins λ₀ alone, and the correlation is weak. 06b's targets are above 0.5 and below 0.2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lam_high_pod_low
|
tuple[float, float]
|
(λ₀ [day⁻¹], Q₅₀) of the confounded regime, with Q₅₀ above most of the marks. |
required |
lam_low_pod_high
|
tuple[float, float]
|
(λ₀, Q₅₀) of the separated regime, with Q₅₀ below them. |
required |
key
|
Array
|
PRNG key. |
required |
window
|
tuple[float, float]
|
The observed window [days]. |
(0.0, 100.0)
|
mu
|
float
|
Log-median μ of the lognormal marks. |
0.0
|
sigma
|
float
|
Log-scale σ of the marks. |
1.0
|
slope
|
float
|
k of the Hill curve, fixed. |
2.0
|
q50_std
|
float
|
Log-scale spread of the Q₅₀ prior, centred on the truth. |
0.5
|
max_ground
|
int
|
Refuses a regime with more ground events than this. |
5000
|
num_warmup
|
int
|
NUTS warm-up steps. |
500
|
num_samples
|
int
|
NUTS draws. |
500
|
Returns:
| Type | Description |
|---|---|
tuple[float, float]
|
|
Raises:
| Type | Description |
|---|---|
ImportError
|
Without the |
ValueError
|
When a regime would simulate more than |
Source code in src/plumax/population/_src/validation.py
prior_swap_test
¶
prior_swap_test(catalog: EmissionCatalog, *, priors: tuple[Callable[[Array], Array], Callable[[Array], Array]], fit: Callable[..., Any] | None = None, n_replicates: int = 8, seed: int = 0, **fit_kwargs: Any) -> float
How far the population posterior moves between per-event priors, in IS σ.
z = max_θ |m̄_A(θ) − m̄_B(θ)| / √((s²_A(θ) + s²_B(θ)) / R), with m̄ and s the mean and standard deviation of the posterior mean over R replicate fits. A replicate resamples each event's draws by π_new / π_old, the same posterior as rerunning the tier under π_new, then refits; the spread s is therefore the importance-sampling noise, with the sampler's. A correct importance correction divides the prior out, so z stays below about 3.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
catalog
|
EmissionCatalog
|
Events with |
required |
priors
|
tuple[Callable[[Array], Array], Callable[[Array], Array]]
|
Two per-event log-priors log π(q). |
required |
fit
|
Callable[..., Any] | None
|
|
None
|
n_replicates
|
int
|
R ≥ 2 per prior. |
8
|
seed
|
int
|
Seed of the resampling and of the fits. |
0
|
**fit_kwargs
|
Any
|
Passed to |
{}
|
Returns:
| Type | Description |
|---|---|
float
|
z, the largest over the fit's parameters. |
Raises:
| Type | Description |
|---|---|
PayloadError
|
When an event lacks its draws or prior. |
ValueError
|
For fewer than 2 replicates. |
Source code in src/plumax/population/_src/validation.py
rank_uniformity_pvalues
¶
rank_uniformity_pvalues(ranks: Int[Array, 'sample param'], n_draws: int, *, seed: int = 0) -> Float[Array, ' param']
KS p-value of SBC ranks against uniform, with ties broken at random.
u = (r + U) / (L + 1), U ~ Uniform(0, 1), is exactly uniform when r is uniform on 0…L, so the discreteness of the ranks costs the KS test nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ranks
|
Int[Array, 'sample param']
|
r per simulation and parameter. |
required |
n_draws
|
int
|
L. |
required |
seed
|
int
|
Seed of the tie-breaking U, for a reproducible p-value. |
0
|
Returns:
| Type | Description |
|---|---|
Float[Array, ' param']
|
The p-value per parameter. |
Examples:
Evenly spread ranks pass, and a pile at one end does not:
>>> import jax.numpy as jnp
>>> even = jnp.arange(200).reshape(-1, 1) % 100
>>> bool(rank_uniformity_pvalues(even, 99)[0] > 0.01)
True
>>> bool(rank_uniformity_pvalues(jnp.zeros((200, 1), dtype=int), 99)[0] < 0.01)
True
Source code in src/plumax/population/_src/validation.py
run_sbc
¶
run_sbc(*, prior_sample: Callable[[Array], Mapping[str, Array]], simulate: Callable[[Array, Mapping[str, Array]], Any], fit: Callable[[Array, Any], Mapping[str, Array]], n_sims: int, key: Array, batch_size: int | None = None) -> SBCResult
Simulation-based calibration of a fit, compiled once.
θ ~ p(θ), y ~ p(y | θ), θ⁽ˢ⁾ ~ p(θ | y), r = #{s : θ⁽ˢ⁾ < θ*}, uniform on 0…L.
The three callables run under jax.lax.map, so they must be traceable, and a
catalog must keep one shape from simulation to simulation (a padded buffer). Only
the ranks are kept, so memory does not grow with the number of draws. The draws
should be close to independent: correlated draws bias r even for a correct fit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prior_sample
|
Callable[[Array], Mapping[str, Array]]
|
|
required |
simulate
|
Callable[[Array, Mapping[str, Array]], Any]
|
|
required |
fit
|
Callable[[Array, Any], Mapping[str, Array]]
|
|
required |
n_sims
|
int
|
Simulations. |
required |
key
|
Array
|
PRNG key. |
required |
batch_size
|
int | None
|
|
None
|
Returns:
| Name | Type | Description |
|---|---|---|
The |
SBCResult
|
class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
For |
Examples:
A conjugate Gaussian fit is calibrated by construction:
>>> import jax
>>> import jax.numpy as jnp
>>> def prior_sample(key):
... return {"mean": jax.random.normal(key)}
>>> def simulate(key, truth):
... return truth["mean"] + jax.random.normal(key)
>>> def fit(key, y):
... return {"mean": 0.5 * y + jnp.sqrt(0.5) * jax.random.normal(key, (99,))}
>>> result = run_sbc(
... prior_sample=prior_sample,
... simulate=simulate,
... fit=fit,
... n_sims=300,
... key=jax.random.PRNGKey(0),
... batch_size=300,
... )
>>> result.param_names, result.n_draws
(('mean',), 99)
>>> bool(result.ks_pvalues[0] > 0.01)
True
Source code in src/plumax/population/_src/validation.py
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | |