ErrorDistribution#

class impulso.protocols.ErrorDistribution(*args, **kwargs)[source]#

Bases: Protocol

Contract for observation error distributions.

An ErrorDistribution owns how the observation error enters the model: which likelihood is registered inside PyMC, and how standardised innovations are drawn on the forecast side. It does not own the error’s scale — that belongs to the VolatilityProcess, which supplies the Cholesky factor L of the scale matrix Ω = L Lᵀ. Concrete adapters: Gaussian (the default) and StudentT.

Under heavy-tailed adapters Ω is the scale matrix, not the covariance; variance_inflation supplies the factor relating the two, which is what FittedVAR.innovation_covariance multiplies sigma() by. See docs/adr/0007-student-t-errors-use-the-scale-matrix-convention.md.

RNG contract:

draw_standardised_innovations must consume rng.standard_normal first, before any other generator call. The Gaussian adapter consumes nothing else, so its stream is a strict prefix of every other adapter’s and seeded Gaussian forecasts stay bit-identical to releases predating this seam. FittedVAR.forecast and the scenario engine share that stream order, which is what makes a matched-seed conditional_forecast with no pins equal forecast() draw for draw.

Note

This is a single Protocol covering both the PyMC-side (build_likelihood) and query-side (draw_standardised_innovations, variance_inflation) surfaces, unlike the volatility seam, which is split into VolatilityProcess and PyMCVolatilityProcess. Both adapters here implement all three methods, so the split would buy nothing today. Splitting is warranted the moment a query-only adapter arrives — an error distribution reconstructed from a stored posterior that can never rebuild its own likelihood.

build_likelihood(name, mu, chol, observed, dims=None)[source]#

Register the observation likelihood in the active PyMC model.

Parameters:
  • name (str) – Name for the observed random variable (the pipeline uses “obs”).

  • mu (pt.TensorVariable) – Conditional mean tensor of shape (T, n_vars).

  • chol (pt.TensorVariable) – Lower-triangular Cholesky factor of the scale matrix Ω, shape (n_vars, n_vars) or (T, n_vars, n_vars).

  • observed (ndarray) – Observed endogenous matrix of shape (T, n_vars).

  • dims (tuple[str, ...] | None) – PyMC dims for the observed variable.

Returns:

The registered PyMC random variable.

Return type:

pt.TensorVariable

draw_standardised_innovations(shape, rng, posterior)[source]#

Draw standardised innovations ξ to be scaled by L.

The forecast recursion adds L_h @ ξ to the conditional mean, so ξ carries the shape of the error law with the scale divided out.

Parameters:
  • shape (tuple[int, ...]) – Draw shape, (chains, draws, n_vars).

  • rng (Generator) – Generator to consume — see the RNG contract on the class.

  • posterior (xr.Dataset) – Posterior Dataset, for adapters whose innovation law depends on estimated parameters (StudentT reads nu).

Returns:

Array of shape shape.

Return type:

ndarray

is_heavy_tailed: bool#

Whether the error law has heavier-than-Gaussian tails. False for Gaussian; True for StudentT. Drives the cross-seam rejection in VAR._validate_spec and the Gaussian-only guards on FittedVAR.conditional_forecast / IdentifiedVAR.structural_scenario, so neither has to string-sniff the adapter name.

logp(mu, chol, value)[source]#

Symbolic log-density of value under the law build_likelihood registers.

Returns exactly the density build_likelihood would register as an observed likelihood — same PyMC distribution, same parameterisation (Student-t keeps the ADR-0007 scale-matrix convention) — but as a free-standing scalar tensor summed over the leading (time) axis, rather than a registered observed random variable.

Must be called inside an active PyMC model context. Adapters that register auxiliary variables in build_likelihood (StudentT registers nu_excess/nu when nu=”infer”) register the same names with the same priors here, so a model built entirely from logp carries an identical posterior to one built from build_likelihood.

This is what lets a later stage add the VAR likelihood as a pm.Potential when the endogenous block is a latent tensor rather than observed data: value is then a symbolic PyTensor variable instead of a NumPy array, and this method is called in place of build_likelihood, not alongside it, so no variable is registered twice.

Parameters:
  • mu (pt.TensorVariable) – Conditional mean tensor of shape (T, n_vars).

  • chol (pt.TensorVariable) – Lower-triangular Cholesky factor of the scale matrix Ω, shape (n_vars, n_vars) or (T, n_vars, n_vars).

  • value (pt.TensorVariable | np.ndarray) – Endogenous matrix, observed or a symbolic latent, shape (T, n_vars).

Returns:

Scalar symbolic log-density, summed over time.

Return type:

pt.TensorVariable

variance_inflation(posterior)[source]#

Factor converting the scale matrix Ω into the innovation covariance.

Cov[y_t] = variance_inflation * Ω. 1.0 for Gaussian errors; nu/(nu-2) per draw for Student-t.

Parameters:

posterior (xr.Dataset) – Posterior Dataset.

Returns:

A scalar, or an array of shape (chains, draws) broadcastable against the leading dims of FittedVAR.sigma().

Return type:

float | np.ndarray