12. Satellite models

A forecast round publishes far more than the core model carries. The structural model gives output, inflation and the policy rate; the round also has to say something about consumption, unemployment, a savings identity, sector detail. Those come from a satellite layer: small single equations, each fitted on its own, run after the core model and conditional on it.

rise.satellite is that layer — the third family of model object, alongside the DSGE and the VAR.

m = rise.satellite([
    "diff_log(consumption) = a1 + a2*diff_log(gdp)"
    "unemployment          = b1 + b2*unemployment{-1} + b3*diff_log(gdp)"
    "savings             === gdp - consumption"
    ]);

12.1. How it connects to the core model

Through the data, not through an object. A satellite system holds equations and parameters and nothing else: it has no handle on a dsge_model, it is not estimated jointly with one, and nothing feeds back from it into the core.

The workflow is:

core = perfect_foresight(m_dsge, 'simul_historical_data', plan);   % or simulate(...)
detail = simulate(m_sat, core, periods);

gdp in the equations above is simply a name the system reads out of the databank you hand it. Where that databank came from is your business — the core model, the published data, or a mixture. That is what makes the layer a satellite: it orbits the core model, and it is the round that puts them together.

12.2. Write the transform, get the level

The point of the object is the left-hand side. Modellers write these equations in growth rates; everyone downstream needs levels.

diff_log(consumption) = ... is fitted and simulated in log differences, and simulate returns the level of consumption. You never write the accumulation. The transforms are the same seven shared with simulation plans (see Conditioning on transforms), so diff_log means the same thing in both places.

=== marks an accounting identity. It is never estimated and never gets a residual.

12.3. Estimation, equation by equation

A name in an equation is a variable until it is declared a parameter. Declaring the coefficients is what tells the system which names to fit:

m.Parameters.a1 = 0;   m.Parameters.a2 = 0;
info = estimate(m, history, 2:n);

The system is recursive by construction, so ordinary least squares equation by equation is consistent and there is nothing to gain from estimating them jointly. info carries the estimates, their standard errors and the R-squared of each equation.

Estimating a system whose coefficients were never declared fits nothing, and says so rather than reporting a clean result with no estimates — that is how somebody comes to believe a model was estimated when it was not.

12.4. Residual recovery, and why it has to be exact

The two operations a forecast round actually runs:

recovered = residuals(m, history, 2:n);   % what residuals does the data imply?
back      = simulate(m, recovered, 2:n);  % put them back

These must be exact inverses of each other, through the transform inversion. They are, to about 1e-13. If they were not, every add-factor a forecaster applies would be quietly wrong, and nobody would find out, because the path would still look plausible. That property is the reason to use this object rather than hand-rolling the accumulation per equation.

12.5. The horizon belongs to the caller

simulate(m, data, (n+1):(n+12)) runs past the end of the data. A forecast is by definition periods the data do not reach, so the working array is extended to match. Missing residuals are zero — no judgment is the sensible default.

12.6. What it refuses

The order of solution is worked out from the equations, never guessed at. A system that cannot be made recursive is reported by name:

RISE:satellite:notRecursive   the simultaneous block involves: alpha, beta

Other refusals carry the same prefix: notAnEquation, badLeftHandSide, noExpansion, tooManyTransforms, noInverse, noData.

12.7. See also