1.9. Deterministic and quasi-deterministic solutions
Most of RISE solves models by perturbation around the steady state: the solution is a (possibly high-order) policy function and shocks are treated as random. Deterministic solutions take the opposite stance – the entire path of the exogenous variables is treated as known (perfect foresight), and RISE solves the model’s equations directly along that path. There is no approximation of the policy function: the nonlinear equations are imposed exactly at every date, subject to an initial condition and a terminal condition.
This is the right tool when the experiment is fundamentally about a known future:
a permanent change in an exogenous variable or a parameter (e.g. a tax reform), where the economy transitions from one steady state to another;
anticipated (pre-announced) shocks – agents know today about a change that materialises later;
large disturbances for which a local approximation around the steady state is inaccurate;
occasionally-binding constraints solved by extended path (see Occasionally-binding constraints).
It is not a substitute for stochastic perturbation when you care about risk, ergodic moments, or the effect of uncertainty itself: perfect foresight sets all future shocks to their expected (known) values.
1.9.1. The perfect-foresight problem
Write the model compactly as
where \(y_t\) are the endogenous variables and \(x_t\) the (known) exogenous variables. A perfect-foresight problem is a two-point boundary-value problem:
an initial condition pins the predetermined variables at \(t = 0\) (typically a steady state, or the realized state at the start of the simulation);
a terminal condition pins \(y_{T+1}\) (typically the steady state the economy converges back to);
the solver finds the interior path \(y_1, \dots, y_T\) that satisfies every equation at every date.
Stacking the \(T\) blocks gives one large nonlinear system in
\(T \times\) endo_nbr unknowns. RISE solves it with a
stacked Newton method (see perfect_foresight).
A first example
Consider a small growth model with capital k, consumption
c and an exogenous x. Suppose the economy starts away
from its steady state – say capital is at half its long-run
value – and we want the transition back. After parsing the model
and solving its steady state, build a simulation plan with
simplan, set the initial condition, and call
perfect_foresight:
m = dsge_model('my_growth_model');
m = set(m, parameters = my_params());
m = sstate(m, sstate_file = @my_sstate);
ss = get(m, 'sstate');
horizon = 200;
plan = simplan(m, [0, horizon], 1); % dates 0..horizon, 1 page
% initial condition at period 0: capital starts at half the steady state
plan = append(plan, 'Capital', 0, ss.Capital/2);
[sims, fval, retcode] = perfect_foresight(m, simul_historical_data = plan);
sims is a structure of time series (one per variable);
fval is the final (max-abs) residual of the stacked system;
retcode is 0 on success (use decipher(retcode)
otherwise). The terminal condition defaults to the model’s
steady state, so the path naturally settles back.
1.9.2. Setting the conditions
The simulation plan carries all the boundary information. The
most direct way to set it is append, which pins a variable
to a value at one or more dates:
plan = append(plan, 'Capital', 0, ss.Capital/2); % initial condition
plan = append(plan, 'x', 2, 0.9); % a known shock at t=2
plan = append(plan, S, 0); % a whole struct at date 0
Anything left free (NaN) at the start and end falls back to
the steady state; free interior entries are what the solver
computes.
Initial and terminal steady states
A permanent-change experiment has a different initial and
terminal steady state. The clean way to express it is to solve
the steady state of the terminal environment and run
perfect_foresight on that model – its steady state then
supplies both the terminal condition and the long-run value of
the exogenous variables – while pinning the period-0
endogenous variables to the initial steady state. There is
no need to solve a separate “terminal steady state” by hand: the
terminal block of the model is solved as part of the problem.
Note
simplan also offers initval / endval / histval
for setting the initial, terminal and historical (lagged)
conditions of the plan – see
Simulation plans. For new work,
passing the conditions directly with append is usually
clearer.
Initial policy multipliers: timeless vs reoptimize
For a commitment / optimal-policy model the initial condition
includes the policy Lagrange multipliers, and there are two
standard conventions for where they start.
simul_optimal_policy_start selects between them. The multiplier
rows are identified by the model’s is_lagrange_multiplier flag
– not by any naming convention:
'timeless'Start the multipliers at their steady-state values: the planner behaves as if it had always been committed (the timeless perspective).
'reoptimize'Start the multipliers at zero: the planner re-optimises at \(t_0\), ignoring past commitments (Ramsey from \(t_0\)).
''(default)Path-appropriate –
'timeless'on the standard path (where the steady state is unique),'reoptimize'on the free-terminal ('stationary') path. On the free terminal the steady state is non-unique, so'timeless'has no target there and raises an error.
[sims, fval, rc] = perfect_foresight(m, ...
simul_optimal_policy_start = 'reoptimize', ...
simul_historical_data = plan);
Individual initial multipliers can always be overridden by pinning them explicitly in the plan.
1.9.3. Free terminal: commitment and optimal-policy models
The default terminal condition is the model’s steady state. For commitment / optimal-policy models – Ramsey, loose commitment, open-loop Nash and the like – that does not work: the first-order conditions give the policy multipliers a martingale law of motion (a unit root), so the terminal steady state is not unique and cannot be solved up front. With the default terminal condition the steady-state solve fails and the perfect-foresight problem never runs.
The simul_terminal_condition option selects how the terminal
is treated:
'steadystate'(default)The terminal is pinned to the model’s solved steady state.
'stationary'The terminal is left free and pinned by stationarity (\(y_{T+1} = y_T\)), solved jointly with the interior path. No pre-solved terminal steady state is required.
[sims, fval, rc] = perfect_foresight(m, ...
simul_terminal_condition = 'stationary', ...
simul_periods = T, ...
simul_historical_data = guess);
The solution is one point on a manifold. Because the terminal
level is genuinely free, many terminal levels are consistent with
stationarity – the solution set is a manifold, not a point.
perfect_foresight returns one point on it, determined
entirely and reproducibly by the warm start and the solver
settings you supply. It is a valid commitment solution; it is
not, in general, a unique one. To select a specific point – for
instance to match a particular reference path – pin the relevant
terminal entries through the plan.
The warm start matters. Supply a guess path through
simul_historical_data (a database or a simplan); each
date’s column seeds that period. The basin of convergence can be
narrow, so a reasonable economic guess is worth providing.
Solver options. The free-terminal system is solved by a
least-squares method (the unit root makes the stacked system
rank-deficient, which a least-squares solve handles gracefully).
The iteration cap and tolerances are passed through
simul_stack_solve_algo. The tolerance that governs the
residual floor here is OptimalityTolerance – tightening it
drives the residual lower at the same solution point (it
sharpens how exactly the equations are satisfied; it does not move
the point):
[sims, fval, rc] = perfect_foresight(m, ...
simul_terminal_condition = 'stationary', ...
simul_historical_data = guess, ...
simul_stack_solve_algo = {'lsqnonlin', ...
struct('MaxIterations', 2000, 'OptimalityTolerance', 1e-10)});
Checking the multiplicity. The dimension of the solution
manifold at a given solution can be reported on demand with
rise.engine.solvers.perfect_foresight.multiplicity_diagnostic.
It is a separate diagnostic, never part of the solve.
Note
When a commitment model also carries an occasionally-binding
constraint, the kink-smoothing enforcement shocks are
unnecessary in a deterministic solve – there the
complementarity is imposed exactly. Turn them off at
construction with dsge_model(..., 'smooth_kinks', false);
see Occasionally-binding constraints.
1.9.4. Choosing the horizon
The horizon \(T\) is not innocuous, and there is a genuine trade-off:
Too short and the terminal steady state is imposed before the transition has actually died out. The boundary condition is then inconsistent with the dynamics, and the solver either fails to drive the residuals to zero or returns a distorted path. The horizon must outlast the slowest stable transient – a rule of thumb is several times the half-life of the dominant stable eigenvalue of the first-order solution.
Too long and the stacked system grows: the residual/Jacobian evaluation cost is linear in \(T\), but the cost of the linear solve grows faster, and memory with it.
When in doubt, err long and lean on the solver options below to keep the cost down (reusing the factorization makes a longer horizon much cheaper).
1.9.5. Solvers and performance
The stacked system is solved by the algorithm named in
simul_stack_solve_algo:
'sparse'(default)A damped Newton method with an Armijo line search and a robust sparse linear solve (
rise_newton). Appropriate for the square stacked system and the default choice.'blocktridiag'The same damped Newton as
'sparse', but the linear solve exploits the block-tridiagonal-in-time structure of the stacked Jacobian with a block-Thomas (Laffargue-Boucekkine-Juillard) forward/backward sweep instead of a general sparse LU. It can be faster when the per-period blocks are dense; where the stacked system is not cleanly block-tridiagonal it falls back to the sparse LU solve, so the path is unchanged.'gmres'/'bicgstab'The same damped Newton with a preconditioned Krylov linear solve (GMRES or BiCGStab). A sparse LU factorization is computed once and reused as the preconditioner across Newton steps, so a long horizon pays one factorization rather than one per iteration.
'lsqnonlin'/'fsolve'MATLAB’s nonlinear least-squares / root-finding solvers.
lsqnonlinhonours box constraints on the variables;fsolvedoes not.- a function handle, a
{algo, opts}cell, or a nested fallback chain { {'sparse', opts1}, {'lsqnonlin', opts2} }tries each in turn until one succeeds.
Two block decompositions are exploited automatically. First, RISE block-triangularises the system, so recursive (purely static / backward / forward) blocks are solved cheaply and only the genuinely simultaneous block carries the full stacked solve. Second, within a block the per-period \([A_{-},\, A_0,\, A_{+}]\) structure is assembled directly as a sparse Jacobian.
Reusing the Jacobian factorization
For large horizons the dominant cost is building and
factorizing the stacked Jacobian. A full Newton step
refactorises every iteration, but near the solution the Jacobian
barely changes, so the factorization can be reused. The
'sparse' solver supports this through the JacobianUpdate
option (passed inside the solver options):
1(default) – full Newton: rebuild and refactorise every iteration.inf– modified Newton: factorize once and reuse the factors, refreshing adaptively only when the line search has to backtrack (and retrying with a fresh factorization if a reused one stalls).any
K > 1– refactorise everyKiterations.
[~, opts] = generic_tools.reset_optim_options('sparse', 'solve');
opts.JacobianUpdate = inf; % factor once, reuse
[sims, fval, rc] = perfect_foresight(m, ...
simul_historical_data = plan, ...
simul_stack_solve_algo = {'sparse', opts});
Reuse trades quadratic for linear convergence but makes each iteration far cheaper; on transition problems it leaves the solution unchanged while substantially reducing the time spent in the linear solve.
Reusing the steady state
perfect_foresight solves the model’s steady state before
simulating. When the model handed in is already solved and its
parameters have not changed – for instance when running several
simulations in a loop, or an extended path – the re-solve is
wasteful. Set simul_reuse_sstate to true to skip it and
use the model’s stored steady state:
[sims, fval, rc] = perfect_foresight(m, ...
simul_historical_data = plan, ...
simul_reuse_sstate = true);
The default is false so the normal solve-on-demand behavior
is unchanged.
Other options
simul_enforce_hybridForce every block to be solved as a hybrid (both backward- and forward-looking) block, bypassing the static/forward/backward classification.
simul_pf_boundsA structure of box constraints on the endogenous variables held during the simulation.
Convergence and tolerances
Convergence of the 'sparse' solver is judged on the
max-abs (infinity norm) of the residual, not the 2-norm.
This matters at scale: the 2-norm of a stacked residual grows
like \(\sqrt{T \times \text{endo\_nbr}}\), so a fixed 2-norm
tolerance becomes unreachable for long horizons even when every
individual equation residual is tiny. The max-abs criterion is
per-equation and therefore size-independent.
The tolerance you can meaningfully demand is bounded by the accuracy of the Jacobian. With an exact (symbolic) Jacobian the residual can be driven to machine precision; the achievable floor is otherwise set by the differentiation method. There is no need to scale the tolerance with the number of equations – the max-abs criterion already does that.
1.9.6. Quasi-deterministic solutions: the extended path
The pure perfect-foresight solve assumes the agents’ information about the future never changes. The extended path relaxes this: the economy is re-solved period by period, and at each date new (“surprise”) information can arrive. This is the natural setting for stochastic simulation of nonlinear models and for occasionally-binding constraints, where the binding / slack pattern is discovered as the simulation unfolds.
In RISE this is triggered by supplying a multi-page simulation
plan (npages > 1): page 1 is the realized/base path and
pages 2:npages carry the conditioning information revealed
at each date. At every pass the solver window shrinks by one
period; when no surprise arrives at a date and a previous
solution exists, RISE shifts that solution by one period rather
than re-solving (a cheap “no-surprise” shortcut). The output
then additionally carries an expectations field with the
step-ahead expectations formed at each date.
Conditional forecasting – pinning future endogenous variables
and letting RISE back out the shocks that achieve them – is a
closely related use of the same machinery; see
Forecasting and simulation and simplan.
1.9.7. Regime switching
Unlike most perfect-foresight tools, RISE carries a regime sequence through the deterministic solve: each period can be assigned a Markov-chain regime, and the per-period equations and steady-state values are evaluated for that regime. This makes it possible to run deterministic simulations under known or anticipated regime changes – a capability that combines RISE’s Markov-switching machinery with the perfect-foresight algorithm.
1.9.8. See also
Zero-th order approximation: steady state and balanced-growth path – the boundary conditions of every deterministic problem.
Occasionally-binding constraints – extended path and piecewise-linear approaches to OBC.
perfect_foresight,simplan,simulate– the API.