LongRunRestriction#

class impulso.identification.LongRunRestriction(*, ordering, shock_names=None, on_undefined='nan', max_condition=100000000.0)[source]#

Bases: ImpulsoModel

Long-run (cumulative) zero restrictions, Blanchard-Quah style.

Cholesky and sign restrictions constrain the impact matrix Theta(0) = P. This scheme constrains the cumulative matrix Theta(1): the total effect of each structural shock on the level of each variable, summed over all horizons. For a stable reduced-form VAR with moving-average coefficients Phi_h, the long-run multiplier is

C(1) = sum_h Phi_h = (I - sum_j A_j)^-1 = M^-1, M = I - sum_j A_j,

and the cumulative structural impact is Theta(1) = C(1) P. The restriction imposed here is that Theta(1) is lower-triangular in the coordinates given by ordering: shock j has no long-run effect on the variables ordered before it. In the Blanchard-Quah (1989) two- variable case — ordering=[“output_growth”, “unemployment”], shock_names=[“supply”, “demand”] — the single zero says the demand shock has no permanent effect on the level of output.

Construction is closed-form. Theta(1) Theta(1)’ = C(1) Sigma C(1)’, so the lower-triangular positive-diagonal Theta(1) is the Cholesky factor of that matrix and P = M Theta(1). Internally the equivalent QR route is used: with L L’ = Sigma, factor (M^-1 L)’ = Q R and set P = L Q (sign-fixed so diag(R) > 0). Because Q is orthogonal, P P’ = Sigma holds to machine precision, and the conditioning of C(1) Sigma C(1)’ is never squared. It also shows what the scheme is: a rotation of the Cholesky factor, chosen in closed form rather than searched for.

Sign convention: the diagonal of Theta(1) is positive, so shock j raises variable j’s long-run level. The diagonal of P itself may be negative — the normalisation is on the cumulative matrix, not on impact.

Restriction count: triangularity is n(n-1)/2 restrictions, exactly the number needed for point identification. For n = 2 that is the single restriction users usually want. For larger n it asserts every zero above the diagonal, which is a strong joint claim. Arbitrary (non-recursive) long-run zero patterns are out of scope, as are partial long-run identification and mixed short-run/long-run schemes (Gali 1999).

Two failure modes are reported separately:

  • M near-singular (C(1) numerically undefined). Controlled by on_undefined and max_condition.

  • Explosive draws (companion spectral radius above one). M may be perfectly well conditioned while sum_h Phi_h diverges, so the arithmetic succeeds but the interpretation does not. Those draws are always returned finite and always warned about.

Parameters:
ordering#

Variable names ordered most long-run-restricted first, mirroring Cholesky.ordering. The returned matrix keeps its rows in the data’s own variable order; ordering only defines the coordinates in which Theta(1) is triangular.

Type:

list[str]

shock_names#

Labels for the shock columns, in the same order as ordering. None (default) reuses ordering as the labels. Explicit naming is strongly preferred — the whole point of the scheme is that the columns mean something.

Type:

list[str] | None

on_undefined#

“nan” (default) blanks draws whose M is numerically singular and warns; “raise” errors instead. NaN draws propagate into IRF/FEVD and are rejected outright by the scenario methods, so “raise” catches the problem early.

Type:

Literal[‘nan’, ‘raise’]

max_condition#

Condition-number threshold above which M counts as numerically singular. Default 1e8.

Type:

float

Note

C(1) is the long-run multiplier on the levels of the modelled variables. “No permanent effect on output” therefore requires output to enter the VAR as a growth rate; the library cannot check this for you.

classmethod from_zero_restrictions(restrictions, var_names, shock_names, **kwargs)[source]#

Build the scheme from named long-run zeros instead of an ordering.

Each entry maps a variable to the shocks that must have no long-run effect on it. The pattern must be triangular under some ordering of the variables and some ordering of the shocks — that is what a recursive long-run scheme means — and this constructor recovers both from the restriction counts. Neither var_names nor shock_names need be given in that order; the names are labels, the pattern decides the positions.

Parameters:
  • restrictions (dict[str, list[str]]) – Variable name -> list of shock names with zero long-run effect on it. Variables with no zeros may be omitted.

  • var_names (list[str]) – All endogenous variable names, in any order.

  • shock_names (list[str]) – All shock names, in any order. Their order in the returned scheme is inferred from the pattern.

  • **kwargs – Forwarded to the constructor (on_undefined, max_condition).

Returns:

A LongRunRestriction whose ordering and shock_names reproduce the named pattern.

Raises:

ValueError – If a name is unknown, or the pattern is not triangular under any pair of orderings.

Return type:

LongRunRestriction

identify(L, var_names, posterior=None, data=None, n_lags=None)[source]#

Apply long-run-restriction identification.

Parameters:
  • L (ndarray) – Lower-triangular Cholesky factor, shape (chains, draws, n_vars, n_vars).

  • var_names (list[str]) – Variable names in the data’s natural order.

  • posterior (xr.Dataset | None) – Full posterior; required, because the long-run multiplier is built from the lag coefficients B.

  • data (VARData | None) – Unused. Accepted for Protocol uniformity.

  • n_lags (int | None) – Lag order. Inferred from B’s trailing axis if omitted.

Returns:

Structural shock matrix, shape (chains, draws, n_vars, n_vars). Rows follow var_names (the data’s order); columns follow shock_coords. Draws whose long-run multiplier is numerically undefined are NaN when on_undefined=”nan”.

Raises:

ValueError – If posterior is missing or carries no B, if ordering does not match var_names, or if on_undefined=”raise” and some draw is undefined.

Return type:

ndarray

property last_diagnostics: dict[str, float]#

Diagnostics from the most recent identify() call.

Scheme-prefixed scalars (see CONTEXT.md “Identification diagnostics”), overwritten per call and surfaced onto IdentifiedVAR.shock_matrix().attrs by the pipeline. Returns a copy.

long_run_diagnostics(posterior, n_lags=None)[source]#

Per-draw conditioning and stability of the long-run multiplier.

Parameters:
  • posterior (Dataset) – Posterior Dataset carrying B (the fit’s posterior group).

  • n_lags (int | None) – Lag order. Inferred from B’s trailing axis if omitted.

Returns:

Dict with “condition” — the condition number of M = I - sum_j A_j, shape (chains, draws) — and “spectral_radius”, the companion spectral radius, same shape. A large condition number means C(1) is barely defined; a spectral radius above one means the long-run sum diverges.

Return type:

dict[str, ndarray]

model_config = {'frozen': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(context, /)#

This function is meant to behave like a BaseModel method to initialize private attributes.

It takes context as an argument since that’s what pydantic-core passes when calling it.

Parameters:
  • self (BaseModel) – The BaseModel instance.

  • context (Any) – The context.

Return type:

None

shock_coords(n_vars)[source]#

Explicit shock names if given, otherwise the ordering.

Parameters:

n_vars (int)

Return type:

list[str]