Importance correction¶
The importance-corrected TMTPP mark likelihood: per-event evidence in the POINT, GAUSSIAN and FULL regimes, which divides each event's per-event prior out of its posterior; the ESS and Pareto-k̂ diagnostics; log-space quadrature for the mark integrals and the thinned rate; and the NumPyro models and fit that use them (mark_iw_model, tmtpp_iw_model, fit_size_distribution_iw, in plumax.population.fitting). Needs the population extra. Tier V.
importance — per-event evidence and ESS¶
Importance-corrected per-event evidence (implementation in plumax.population._src.importance).
EventEvidence
¶
Bases: Module
Nodes and log-weights of one event's (or a stack of events') mark integral.
The mark term is log Σₛ exp(ℓₛ + g(qₛ)) for g = log P_d + log f(· | θ).
Attributes:
| Name | Type | Description |
|---|---|---|
log_weights |
Float[Array, '*event member']
|
ℓₛ, including −log π_i(qₛ). |
q_samples |
Float[Array, '*event member']
|
The nodes qₛ [kg/s]. |
log_terms
¶
ℓₛ + g(qₛ), with nodes at Q ≤ 0 excluded (−∞).
g is evaluated on a positive stand-in at excluded nodes, so a POD or mark that is undefined there cannot turn the sum into NaN.
Source code in src/plumax/population/_src/importance.py
log_mark_term
¶
log Σₛ exp(ℓₛ + g(qₛ)), per event.
effective_sample_size
¶
effective_sample_size(log_integrand: Callable[[Array], Array] | None = None) -> Float[Array, '*event']
ESS = (Σₛ wₛ)² / Σₛ wₛ² of wₛ = exp(ℓₛ + g(qₛ)), per event.
Without log_integrand the weights are the prior correction alone.
Source code in src/plumax/population/_src/importance.py
Regime
¶
Bases: StrEnum
How an event's per-event posterior enters the mark integral.
catalog_evidence
¶
catalog_evidence(catalog: EmissionCatalog, *, regime: Regime | str = Regime.FULL, n_nodes: int = 32, measure: Measure = 'lognormal') -> EventEvidence
:func:event_evidence for every event, stacked along a leading axis.
Raises:
| Type | Description |
|---|---|
ValueError
|
For an empty catalog, or FULL draws of unequal length. |
Source code in src/plumax/population/_src/importance.py
effective_sample_size
¶
ESS = (Σₛ wₛ)² / Σₛ wₛ² from log-weights, along the last axis.
Examples:
Source code in src/plumax/population/_src/importance.py
ess_report
¶
ess_report(catalog: EmissionCatalog, *, log_integrand: Callable[[Array], Array], regime: Regime | str = Regime.FULL, measure: Measure = 'lognormal', warn: bool = True) -> np.ndarray
Per-event ESS of the importance weights at one θ, with a collapse warning.
wₛ = P_d(qₛ) f(qₛ | θ) / π_i(qₛ) for g = log P_d + log f(· | θ). Warns (does not fail) when an event's ESS < 0.1 S or, in the FULL regime, its Pareto k̂ > 0.7.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
catalog
|
EmissionCatalog
|
The events. |
required |
log_integrand
|
Callable[[Array], Array]
|
g, e.g. |
required |
regime
|
Regime | str
|
How the events enter. |
FULL
|
measure
|
Measure
|
The GAUSSIAN-regime summary (see :func: |
'lognormal'
|
warn
|
bool
|
Emit the collapse warning. |
True
|
Returns:
| Type | Description |
|---|---|
ndarray
|
ESS per event. |
Source code in src/plumax/population/_src/importance.py
event_evidence
¶
event_evidence(event: EmissionEvent, *, regime: Regime | str = Regime.FULL, n_nodes: int = 32, measure: Measure = 'lognormal') -> EventEvidence
Nodes and log-weights of one event's importance-corrected mark integral.
In the GAUSSIAN regime the posterior is replaced by its two-moment summary,
a lognormal (the 06a default) or a normal in Q (measure="normal", the
family of the Gaussian Tier II–IV posteriors). The prior is divided out
exactly, so the summary's tails must not be heavier than the prior's: when
the summary's likelihood p̃/π rises at an outermost Hermite node more than
TAIL_TOLERANCE nats above its interior maximum, the approximation is
rejected, since the quadrature would then be driven by its extreme nodes.
Nodes at Q ≤ 0 carry no weight (every registry mark lives on Q > 0).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event
|
EmissionEvent
|
The event, with |
required |
regime
|
Regime | str
|
POINT, GAUSSIAN or FULL. |
FULL
|
n_nodes
|
int
|
Gauss–Hermite nodes for the GAUSSIAN regime. |
32
|
measure
|
Measure
|
|
'lognormal'
|
Returns:
| Name | Type | Description |
|---|---|---|
The |
EventEvidence
|
class: |
Raises:
| Type | Description |
|---|---|
PayloadError
|
Without the per-event prior, without draws for FULL, or when the GAUSSIAN summary's tails outweigh the prior's. |
ValueError
|
For a non-positive emission rate outside FULL, or an unknown measure. |
Source code in src/plumax/population/_src/importance.py
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | |
warn_on_collapse
¶
Warn when an event's ESS < 0.1 S or its Pareto k̂ > 0.7.
Source code in src/plumax/population/_src/importance.py
integrate — log-space quadrature¶
Log-space integration for the Tier V mark likelihood (implementation in plumax.population._src.integrate).
log_gauss_hermite
¶
log_gauss_hermite(log_f: Callable[[Array], Array], mu: ArrayLike, sigma: ArrayLike, *, n: int = 32) -> Float[Array, '']
log ∫ f(q) LN(q; μ, σ²) dq by Gauss–Hermite quadrature in log space.
With q = exp(μ + √2 σ x), the integral is π^(−½) Σₖ wₖ f(qₖ) for the Hermite nodes xₖ and weights wₖ, evaluated as a log-sum-exp so that f(qₖ) never leaves log space.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_f
|
Callable[[Array], Array]
|
log f, on an array of rates. |
required |
mu
|
ArrayLike
|
μ, the mean of log Q. |
required |
sigma
|
ArrayLike
|
σ > 0, the standard deviation of log Q. |
required |
n
|
int
|
Number of Hermite nodes (static). |
32
|
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
The log-integral. |
Examples:
>>> import jax.numpy as jnp
>>> # E[Q] = exp(μ + σ²/2) for a lognormal Q
>>> value = log_gauss_hermite(jnp.log, 0.2, 0.5)
>>> bool(jnp.allclose(value, 0.2 + 0.5 * 0.5**2))
True
Source code in src/plumax/population/_src/integrate.py
log_simpson
¶
log_simpson(log_f: Callable[[Array], Array], q_min: float, q_max: float, *, n: int = 2001) -> Float[Array, '']
log ∫ f(q) dq over [q_min, q_max] by composite Simpson in u = log q.
∫ f(q) dq = ∫ f(eᵘ) eᵘ du, with Simpson weights on a uniform u grid, as a log-sum-exp. A uniform grid in log q resolves a lognormal body and a power-law tail alike, which a uniform grid in q cannot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_f
|
Callable[[Array], Array]
|
log f, on an array of rates. |
required |
q_min
|
float
|
Lower limit, > 0. |
required |
q_max
|
float
|
Upper limit. |
required |
n
|
int
|
Number of nodes (static; made odd). |
2001
|
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
The log-integral. |
Examples:
>>> import jax.numpy as jnp
>>> # ∫₁^∞ 3 q⁻⁴ dq = 1, truncated at 1e6
>>> value = log_simpson(lambda q: jnp.log(3.0) - 4.0 * jnp.log(q), 1.0, 1e6)
>>> bool(jnp.abs(value) < 1e-8)
True
Source code in src/plumax/population/_src/integrate.py
pareto_smoothed_log_weights
¶
Pareto-smoothed importance log-weights and the tail shape k̂.
The largest M = ⌈min(S/5, 3√S)⌉ weights are replaced by the expected order statistics of a generalised Pareto fitted to their exceedances, then the weights are normalised. k̂ > 0.7 flags weights too heavy-tailed for the importance estimate to be trusted. A host-side diagnostic (NumPy, not traced): the fit sorts the weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_weights
|
ArrayLike
|
Unnormalised log importance weights. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
float
|
the fitted shape (NaN when fewer than five weights exceed the cutoff, |
tuple[ndarray, float]
|
e.g. equal weights, which need no smoothing). |
Raises:
| Type | Description |
|---|---|
ImportError
|
Without the |
Examples:
>>> import numpy as np
>>> log_weights = np.random.default_rng(0).normal(0.0, 0.3, 1000)
>>> smoothed, k_hat = pareto_smoothed_log_weights(log_weights)
>>> bool(np.isclose(np.logaddexp.reduce(smoothed), 0.0)), k_hat < 0.7
(True, True)
References
Vehtari, Simpson, Gelman, Yao & Gabry, Pareto smoothed importance sampling, arXiv:1507.02646. Zhang & Stephens (2009), Technometrics.
Source code in src/plumax/population/_src/integrate.py
Importance-corrected models¶
Importance-corrected population fits: the mark-only fit and the full TMTPP.
Both score detection i by the importance-corrected mark term of
:mod:~plumax.population.importance,
mᵢ(θ) = log Σₛ exp(ℓᵢₛ + log P_d(qᵢₛ) + log f(qᵢₛ | θ)),
with the thinned rate p̄(θ) = ∫ P_d(q) f(q | θ) dq in log space
(:func:~plumax.population.integrate.log_simpson; p̄ = 1 without a POD).
- :func:
mark_iw_model: the marks given the detections, Σᵢ mᵢ(θ) − N log p̄(θ), the λ-free part of the likelihood. - :func:
tmtpp_iw_model: the full TMTPP, Σᵢ [log λ(tᵢ) + mᵢ(θ)] − Λ(T) p̄(θ), with the ground intensity from the intensity registry on xtremax (:func:~plumax.population.processes.make_process). - :func:
fit_size_distribution_iw: NUTS on :func:mark_iw_model, with the per-event ESS and Pareto k̂ at the posterior-mean θ and a collapse warning. - :class:
TMTPPPosterior: the draws of :func:tmtpp_iw_model, as the intensity kernels and mark parameters the persistency and total-emission products take.
The POD is a fixed callable log_pod, or the union of calibrated instruments
pods (:mod:~plumax.population.pod), whose (Q₅₀, k) are then sampled too.
MarkPosterior
dataclass
¶
MarkPosterior(mark: str, regime: Regime, samples: dict[str, ndarray], ess: ndarray, pareto_k: ndarray)
Posterior over a mark family's parameters, with importance diagnostics.
Attributes:
| Name | Type | Description |
|---|---|---|
mark |
str
|
The registry name of the family. |
regime |
Regime
|
How the events entered. |
samples |
dict[str, ndarray]
|
Posterior draws of θ by parameter name. |
ess |
ndarray
|
Per-event ESS of the importance weights at the posterior-mean θ. |
pareto_k |
ndarray
|
Per-event Pareto k̂ (FULL regime; NaN otherwise). |
posterior_mean
¶
θ̄, the posterior mean of every parameter.
TMTPPPosterior
dataclass
¶
Posterior draws of the full TMTPP, by the site names of its model.
Attributes:
| Name | Type | Description |
|---|---|---|
intensity |
str
|
The registry name of the ground intensity. |
mark |
Mark
|
The mark family. |
samples |
dict[str, Array]
|
Draws from :func: |
kernel
¶
The intensity kernel of one draw.
A kernel's site names need not match its fields (lambda_bg is the
field lambda_background), so the draw goes through sample_priors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
draw
|
dict[str, Array]
|
One draw of :attr: |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The kernel at that draw. |
Source code in src/plumax/population/_src/fitting_iw.py
theta
¶
The mark parameters θ of one draw.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
draw
|
dict[str, Array]
|
One draw of :attr: |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Array]
|
θ by parameter name, as the mark's methods take it. |
Source code in src/plumax/population/_src/fitting_iw.py
log_thinned_rate
¶
log_thinned_rate(mark: Mark, theta: dict[str, Any], log_pod: Callable[[Array], Array] | None, support: tuple[float, float], *, n_nodes: int = 2001) -> Float[Array, '']
log p̄(θ) = log ∫ P_d(q) f(q | θ) dq, 0 without a POD.
Log-space Simpson on [q_lo, q_hi], plus the mass above q_hi at the POD's value there, P_d(q_hi) P(Q > q_hi | θ), so a heavy tail beyond the grid is not dropped. The mass below q_lo is left out: a detection curve is nondecreasing in Q, and q_lo sits far below every detection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mark
|
Mark
|
The mark family. |
required |
theta
|
dict[str, Any]
|
Its parameters. |
required |
log_pod
|
Callable[[Array], Array] | None
|
log P_d, or |
required |
support
|
tuple[float, float]
|
|
required |
n_nodes
|
int
|
Simpson nodes. |
2001
|
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
log p̄. |
Source code in src/plumax/population/_src/fitting_iw.py
default_support
¶
SUPPORT_DECADES decades either side of the evidence nodes.
Source code in src/plumax/population/_src/fitting_iw.py
mark_iw_model
¶
mark_iw_model(evidence: EventEvidence, *, mark: Mark, log_pod: Callable[[Array], Array] | None = None, support: tuple[float, float] | None = None) -> None
NumPyro model of the marks given the detections, importance-corrected.
log L(θ) = Σᵢ mᵢ(θ) − N log p̄(θ), θ under mark.numpyro_prior("mark").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
evidence
|
EventEvidence
|
Stacked per-event evidence (:func: |
required |
mark
|
Mark
|
The mark family. |
required |
log_pod
|
Callable[[Array], Array] | None
|
log P_d on rates in the marks' units, or |
None
|
support
|
tuple[float, float] | None
|
Rate range of the p̄ integral; default from the evidence. |
None
|
Source code in src/plumax/population/_src/fitting_iw.py
tmtpp_iw_model
¶
tmtpp_iw_model(event_times: Float[Array, ' event'], mask: Bool[Array, ' event'], evidence: EventEvidence, *, mark: Mark, intensity: str, window: tuple[float, float], log_pod: Callable[[Array], Array] | None = None, support: tuple[float, float] | None = None, pods: Sequence[InstrumentPOD] | None = None, coverage: Bool[Array, 'event instrument'] | None = None, coverage_intervals: Sequence[tuple[float, float, Bool[Array, ' instrument']]] | None = None, tod_origin: float = 0.0, n_nodes: int = 2001) -> None
NumPyro model of the full importance-corrected TMTPP likelihood.
log L = Σᵢ [log λ(tᵢ) + mᵢ(θ)] − Λ(T) p̄(θ), with λ a registry intensity
under its sample_priors on the xtremax Poisson process of the window.
With pods, P_d is the union P_d^∪ under :func:calibration_prior. With
coverage, event i is thinned by the instruments that overpassed it and the
compensator follows who was looking when, as 06a § Non-detection events asks:
Σ_w Λ_w p̄_w(θ), over intervals w in which the covering set is constant. A
per-event mask alone does not fix that integral, so the two come together. An
instrument with tod_bins shifts its Q₅₀ by the hour of each event, and the
compensator sums over the hours too: Σ_w Σ_b Λ_w,b p̄_w,b(θ).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_times
|
Float[Array, ' event']
|
Detection times since t₀ in xtremax's padded buffer
(:func: |
required |
mask
|
Bool[Array, ' event']
|
Its mask. |
required |
evidence
|
EventEvidence
|
Stacked per-event evidence, one row per detection in the
order of the buffer's real entries. It may also carry the buffer's
padding, one row per buffer entry: the mask drops those rows, so
catalogs of different sizes can share one shape under |
required |
mark
|
Mark
|
The mark family. |
required |
intensity
|
str
|
A registry name of a Poisson intensity. |
required |
window
|
tuple[float, float]
|
|
required |
log_pod
|
Callable[[Array], Array] | None
|
log P_d on rates in the marks' units, or |
None
|
support
|
tuple[float, float] | None
|
Rate range of the p̄ integral; default from the evidence. |
None
|
pods
|
Sequence[InstrumentPOD] | None
|
Calibrated instruments, instead of |
None
|
coverage
|
Bool[Array, 'event instrument'] | None
|
Which instruments overpassed each row of |
None
|
coverage_intervals
|
Sequence[tuple[float, float, Bool[Array, ' instrument']]] | None
|
|
None
|
tod_origin
|
float
|
Local hour of day at t = 0 of |
0.0
|
n_nodes
|
int
|
Simpson nodes of each p̄ integral. |
2001
|
Raises:
| Type | Description |
|---|---|
ImportError
|
Without the |
ValueError
|
For |
EquinoxRuntimeError
|
If a detection has no covering instrument. |
Source code in src/plumax/population/_src/fitting_iw.py
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | |
fit_size_distribution_iw
¶
fit_size_distribution_iw(catalog: EmissionCatalog, *, mark: str = 'lognormal', mark_fields: dict[str, Any] | None = None, regime: Regime | str = Regime.FULL, log_pod: Callable[[Array], Array] | None = None, support: tuple[float, float] | None = None, n_nodes: int = 32, measure: Measure = 'lognormal', num_warmup: int = 500, num_samples: int = 500, num_chains: int = 1, seed: int = 0) -> MarkPosterior
Importance-corrected NUTS fit of the population size distribution.
Runs :func:mark_iw_model, then reports the per-event ESS (and, in the
FULL regime, the Pareto k̂) of the weights at the posterior-mean θ,
warning in every regime when an event collapses (ESS < 0.1 S, or
k̂ > 0.7).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
catalog
|
EmissionCatalog
|
Events with the payload-v2 fields ( |
required |
mark
|
str
|
A registry name (:data: |
'lognormal'
|
mark_fields
|
dict[str, Any] | None
|
The family's fields (support, prior settings). |
None
|
regime
|
Regime | str
|
POINT, GAUSSIAN or FULL. |
FULL
|
log_pod
|
Callable[[Array], Array] | None
|
log P_d on rates in the marks' units, or |
None
|
support
|
tuple[float, float] | None
|
Rate range of the p̄ integral; default from the evidence. |
None
|
n_nodes
|
int
|
Gauss–Hermite nodes of the GAUSSIAN regime. |
32
|
measure
|
Measure
|
The GAUSSIAN-regime summary, |
'lognormal'
|
num_warmup
|
int
|
NUTS warm-up steps. |
500
|
num_samples
|
int
|
Posterior draws per chain. |
500
|
num_chains
|
int
|
NUTS chains. |
1
|
seed
|
int
|
PRNG seed. |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
The |
MarkPosterior
|
class: |
Raises:
| Type | Description |
|---|---|
PayloadError
|
When the events lack the payload the regime needs. |
Source code in src/plumax/population/_src/fitting_iw.py
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 | |