4. Simulation plans
A simulation plan is the object that collects every choice you might want to impose on a forward path – a conditioning value on an endogenous variable at a given date, a pinned or activated shock, whether a constraint is anticipated or hits as a surprise, an algebraic identity that must hold over a window – into a single object the toolbox’s simulators consume.
The plan is the universal interface for controlled simulations in RISE:
simulatedrives the perturbation solution forward through the plan.forecastproduces conditional forecasts (with or without shock uncertainty) from the plan.perfect_foresightsolves the full nonlinear model under the plan as a deterministic boundary-value problem.
The same plan flows into all three; what changes between them is the solver, not the plan. This chapter is the reference for building the plan. The next chapter, Forecasting and simulation, is the reference for the solvers that consume it.
4.1. Why a simulation plan
A plan separates what you want enforced from how the solver enforces it. Two payoffs:
One vocabulary for stochastic and deterministic contexts. A DSGE solved by perturbation accepts anticipated shocks natively, so a plan that announces a shock \(k\) periods ahead – or springs it as a surprise at \(t\) – works identically through
simulateand throughperfect_foresight. You do not switch APIs to switch contexts.Hard endogenous constraints with an explicit identification audit. A plan that pins \(y_t\) at a chosen value requires a free shock at \(t\) to enforce it. Because the plan stores the shock matrix explicitly – pinned entries are numeric, free entries are
NaN– the identification condition is checkable at construction time, before the solver is ever called.
Plans also accept algebraic constraints (a linear combination of variables fixed over a window), bounds (soft inequality constraints, where the corresponding feature is supported), and multi-page anticipation structures (the same shock at the same date entered with several lead times).
4.2. Construction
The constructor takes the model, a horizon, an anticipation horizon, and (optionally) a shock-initialization rule:
sp = simplan(m, [start_date, end_date], anticipationHorizon, ...
'shockInit', initRule);
The four arguments:
m– a solved (or at least parsed)rise_modelobject, used to read the endogenous, exogenous and parameter lists and to anchor dates.[start_date, end_date]– the horizon.start_dateis the last date of history (the point from which the simulation continues forward);end_dateis the last simulation date. Dates may be integers ([1, 40]) or RISE date objects ([rq(2020,1), rq(2030,4)]).anticipationHorizon– the maximum number of periods ahead a shock can be announced.1means contemporaneous only;kallows announcements up to \(k-1\) periods ahead.'shockInit', initRule– the default value for every shock, at every date, before anyappendcalls. See below.
4.2.1. The shockInit rule
shockInit chooses what every shock entry starts as:
Value |
Effect |
|---|---|
|
Every shock pinned at |
|
Every shock drawn from a standard normal. Reproduce by seeding the RNG before construction. |
numeric scalar |
Every shock pinned at the scalar. |
struct |
Per-shock rule, where each field name is a shock and the value is any of the rules above. |
Example:
spec = struct();
spec.EZ = 'randn';
spec.ER = 'zero';
spec.EETA = 'zero';
rng(42)
sp = simplan(m, [rq(2020,1), rq(2030,4)], 1, 'shockInit', spec);
shockInit only sets defaults; later append calls
overwrite the entries they touch.
4.3. Appending conditions
append is the workhorse. Three forms cover almost everything.
4.3.1. Single variable, vector of values
Pin one variable over a window:
target_dates = rq(2022,1):rq(2022,4);
sp = simplan(m, [rq(2020,1), rq(2030,4)], 1, 'shockInit', 'zero');
sp = append(sp, 'PAI', target_dates, 1.005 * ones(4,1));
Numeric values pin the variable. A single scalar is replicated across the date range:
sp = append(sp, 'R', rq(2023,1):rq(2023,4), 1.01);
4.3.2. Multiple variables, struct form
When several variables share the same dates:
S = struct();
S.PAI = 1.005 * ones(4,1);
S.R = 1.010 * ones(4,1);
sp = append(sp, S, rq(2022,1):rq(2022,4));
Scalar fields in the struct are replicated across the dates.
4.3.3. Algebraic constraint
A linear combination of variables can be fixed over a window through a constraint-string and a fixing shock:
sp = append(sp, ...
{'R{t} - 1.005 * PAI{t} = 0', 'ER'}, ...
rq(2022,1):rq(2022,4), ...
NaN);
The constraint string uses the same syntax as model equations
(var{t} for the contemporaneous variable). The fixing shock
(ER here) is the shock the solver is allowed to use to enforce
the constraint – it must be activated (NaN) at the same
dates.
4.4. Boundary conditions: initval, endval, histval
Besides append, a plan exposes Dynare-style setters for the
boundary conditions of a perfect-foresight problem. They are
convenient when porting a Dynare model, or when you think in terms
of initial / terminal / historical blocks rather than
per-date pins.
initval(plan, steady, C)Set the initial conditions (and, in the absence of other blocks, the start values and the terminal condition).
steadyis a logical:truetreatsCas a guess and solves the steady state from it;falseuses the values as given.Cis the conditions –{name, value}pairs, a cell of'name=value;'strings, or a single string:plan = initval(plan, false, {'k', 12; 'c', 1.1}); plan = initval(plan, false, 'k=12; c=1.1;');
endval(plan, steady, C)Set the terminal conditions, with the same
steadyflag and the sameCformats. Use it for a permanent-change experiment whose terminal steady state differs from the initial one:plan = endval(plan, true, {'tau', 0.25}); % solve the terminal SS at tau = 0.25
histval(plan, C)Set the historical (lagged) initial conditions, with the lag written into the name:
plan = histval(plan, {'k(-1)', 12; 'y(0)', 1});
from_history(m, db, history_end_date, nsteps)Build a plan whose initial conditions are read from a historical database at the jump-off date. For every declared endogenous variable present in
db, the jump-off value and its lags are written into the plan; the simulation then projectsnstepsperiods forward:plan = simplan.from_history(m, db, rq(2021,4), 40);
These setters and append combine freely: a common pattern is
histval (or from_history) for the lagged initial state plus
append for known future shocks. For commitment / optimal-policy
models, whether the initial policy multipliers start at the
steady state or at zero is a separate choice – see
simul_optimal_policy_start in
Deterministic and quasi-deterministic solutions.
4.5. Anticipation: pages
Each shock entry sits on a page indexed by lead time:
Page
1– the shock is realised at the date but unanticipated before then.Page
k(fork <= anticipationHorizon) – the shock is announced at the date but realised \(k-1\) periods later; agents see it that many periods in advance.
Pages let surprises and announcements coexist in the same plan.
An anticipated monetary shock at rq(2023,1) announced two
quarters ahead:
sp = append(sp, 'ER', rq(2023,1), 0.01, 3);
(page 3 because the announcement is \(k-1 = 2\) periods
ahead). The same shock and date entered on page 1 would be an
unanticipated realization. The two coexist if you write both.
Sequences of announcements with different lead times are common:
shock_dates = rq(2024,1):rq(2024,4);
shock_values = [0.02; 0.015; 0.01; 0.005];
for lead = 0:3
sp = append(sp, 'EZ', shock_dates(lead+1), ...
shock_values(lead+1), lead+1);
end
Each entry is the same shock at a different date, announced a different number of periods ahead.
4.6. Hard endogenous conditions and the identification rule
Pinning a variable at a chosen value is a hard endogenous condition. The solver enforces it by choosing values for free shocks at the same dates. Two ingredients are required:
The variable is appended with a numeric value at the condition dates.
Enough shocks are activated at the condition dates by being set to
NaN.
At every date \(t\) in the conditioning window:
If the inequality fails, the solver raises an identification error
(active constraints exceed free shocks).
4.6.1. The recipe
A non-trivial conditional forecast follows four steps:
Construct the plan with
shockInit = 'zero'.Append background shocks at fixed values (these stay pinned and shape the baseline scenario).
Activate the shocks the solver may use, by appending them at
NaNat the conditioning dates.Append the endogenous conditions (numeric values at those same dates).
A worked single-variable example – a faster disinflation than the Taylor rule alone would deliver, after a cost-push shock:
horizon = [rq(2020,1), rq(2028,1)];
cond_dates = rq(2021,1):rq(2022,4); % 8-quarter window
sp = simplan(m, horizon, 1, 'shockInit', 'zero');
% Step 2: background -- cost-push shock at the start of history
sp = append(sp, 'EETA', rq(2020,2), 2.0);
% Step 3: activate the monetary shock at the conditioning dates
sp = append(sp, 'ER', cond_dates, NaN);
% Step 4: pin inflation to the target path
PAI_target = linspace(PAI_uncon_peak, PAI_ss, numel(cond_dates)).';
sp = append(sp, 'PAI', cond_dates, PAI_target);
At each date in cond_dates there is exactly one constraint
(PAI) and exactly one NaN shock (ER): the system is
exactly identified and the solver backs out the ER path that
makes inflation follow PAI_target.
Two-variable conditioning is the same pattern with two activated shocks:
sp = simplan(m, horizon, 1, 'shockInit', 'zero');
sp = append(sp, 'EETA', rq(2020,2), 2.0); % background
sp = append(sp, 'ER', cond_dates, NaN); % activate
sp = append(sp, 'EETA', cond_dates, NaN); % activate
sp = append(sp, 'PAI', cond_dates, PAI_target);
sp = append(sp, 'R', cond_dates, R_peg);
Two constraints, two free shocks – exactly identified.
Note
Appending EETA = NaN at cond_dates overrides the zero
that shockInit placed there. The earlier background entry
EETA = 2.0 at rq(2020,2) is untouched – only the
cond_dates entries are overwritten.
4.6.2. Soft conditions
A soft condition restricts a variable to a band instead of
pinning it to a single value. You append a two-element [lo hi]
value where a hard condition would take a scalar:
sp = append(sp, {'Y', cond_dates, [-0.5 0.5]}); % a band
sp = append(sp, {'R', cond_dates, [ 0.0 NaN]}); % one-sided (a floor)
A scalar is a hard point; a [lo hi] pair is a band. An open
side is written NaN – [lo NaN] is a floor and [NaN hi]
a ceiling. The plan recognizes a band automatically:
is_soft(sp) % true when the plan carries at least one (non-degenerate) band
A degenerate band [v v] is just the hard point v – the
hard condition is the zero-width limit of the soft one.
A band is still an endogenous condition, so the identification
rule above applies unchanged: at least as many shocks must be freed
(set to NaN) at each conditioning date as there are conditioned
variables. The convenience method free_shocks does the freeing
in bulk:
sp = free_shocks(sp, 'EPS_D', cond_dates); % free one shock
sp = free_shocks(sp, 'all', cond_dates); % free every shock --
% recovers the classic
% minimum-norm conditioning
How a band is consumed – a simulated distribution (a fan chart)
or a single conditional mean – is chosen on the solver call through
the simul_soft_conditioning option, documented in
Forecasting and simulation. A plan that carries no band is an
ordinary hard-conditioning plan and is solved exactly as described
above.
4.7. Inspecting a plan
Three methods help you check what is in a plan:
disp(sp)– a formatted summary: the horizon, the number of constrained endogenous and exogenous entries, the pages in use.details(sp)– the raw property dump, useful when something indispdoes not match what you expected.query(sp, name, dates, pages)– inspect specific entries.
query examples:
query(sp, 'PAI') % all conditioned PAI dates
query(sp, 'PAI', rq(2022,2)) % a specific date
query(sp, 'ER', rq(2023,1)) % a shock across all pages
query(sp, 'ER', rq(2023,1), 3) % a specific page
query is the right tool to verify the identification rule
before calling the solver – count the NaN entries at each
condition date and compare to the number of pinned endogenous
constraints.
4.8. Consumption: the universal interface
Once a plan is built, the same plan drives every consumer. The
plan is passed through the simul_historical_data option:
res_sim = simulate (m, simul_historical_data = sp);
res_fkst = forecast (m, simul_historical_data = sp, ...
forecast_nsteps = H);
res_pf = perfect_foresight(m, simul_historical_data = sp);
The three solvers differ in what they assume about the model solution and the shock structure, not in how they read the plan:
Solver |
Solution it uses |
When to prefer it |
|---|---|---|
|
The perturbation solution (linear or higher-order) returned
by |
Fast. Accurate near steady state. The default for stochastic conditional simulations. |
|
Same perturbation solution, with options for shock
uncertainty ( |
When you want a true forecast – shocks drawn past the conditioning window, fan charts, the conditional-forecast output structure. |
|
The full nonlinear model solved as a boundary-value problem over the entire horizon. |
More accurate for large or persistent deviations; honours occasionally-binding constraints exactly; for deterministic scenarios. |
The identification rule is solver-independent: a plan that is under-identified for one solver is under-identified for all three. This is by design – the plan separates the problem statement from the solution method.
4.9. Scope: shapes other than DSGE
The simulation-plan object is built around the canonical fields
of the rise_model class and therefore works for every factory
that returns one. The construction interface (horizons, pages,
append patterns, the identification rule) is identical across
shapes.
Each shape’s chapter under Model shapes documents the consumer-side options that are specific to that shape (e.g. structural-shock identification under SVAR conditioning).
4.10. See also
Forecasting and simulation – the consumer side:
simulate,forecast,perfect_foresight, their option surfaces, fan charts, relative-entropy tilting.Filtering – the smoother-based alternative for linear models with conditions on observed variables (no plan required; based on least squares).
Deterministic and quasi-deterministic solutions – the deterministic context in full; uses plans throughout.
Occasionally-binding constraints – scenarios that combine the plan with OBC routes.