2.9. Optimal (Ramsey) policy
When the model file contains an optimization-problem block, RISE treats the policy as the solution to a planner’s problem rather than as a fixed equation: one (or more) of the policy equations is replaced by the planner’s first-order conditions. RISE handles several flavours of this:
commitment (Ramsey policy), with or without regime switching;
discretion, linear-quadratic or nonlinear, with or without regime switching;
loose commitment – switching between commitment and discretion with a constant probability, in a linear-quadratic system;
stochastic replanning – switching between commitment and discretion with possibly time-varying probabilities, in a nonlinear system;
non-cooperative games with multiple policymakers, Nash or Stackelberg, and in the Nash case with a further choice between open-loop equilibrium (OLE, the historical default) and Markov-perfect equilibrium (MPE) – selected at solve time, see below.
If instead you want to keep all the model equations and only pick the coefficients of a given (e.g. Taylor) rule optimally, see Optimal (optimized) simple rules.
2.9.1. Declaring the planner’s problem
The objective and the policy type are declared with the @optimization_problem
block (formerly @planner_objective). When this block is present, the number
of equations must be strictly less than the number of endogenous variables –
the missing equations are the planner’s optimality conditions, generated by
RISE. In skeletal form:
@optimization_problem[@no_u_turn = false, @commitment = 1]{
@objective = pi_c^2 + y^2, @discount = beta }
The most relevant options are @commitment (1 = commitment/Ramsey,
0 = discretion; default 1), @no_u_turn (set true if you will
only solve commitment/loose-commitment problems – it disables the discretion
and stochastic-replanning algorithms but is cheaper), @markov_process (the
Markov process controlling the game; default "const" – no switches), and
the core problem settings @objective and @discount. The full syntax,
including the core problem settings and the regime-switching cases, is in the
model-language reference: see Description
(the @optimization_problem block section).
2.9.2. Choosing the policy type at solve time
If the block does not pin the type down (e.g. @optimization_problem[]{ @objective = ..., @discount = beta }),
select it on the model object before solving:
m = set(m, 'solve_policy_type', 'discretion'); % or 'commitment'
Then solve, stoch_simul, irf, estimate, … behave as usual –
the only difference is which equilibrium they impose. RISE picks an appropriate
solver automatically:
for a constant-parameter problem that eigenvalue methods can handle: the default RISE solver
rise_1(note that discretion can also be solved withrise_1, conditional on the solution of the forward-looking variables);for a regime-switching problem: the functional-iteration solver
mfi;when
@no_u_turn = true: onlyloose_commitmentis appropriate – it works well in linear-quadratic setups but is less accurate for nonlinear problems, and higher-order perturbation cannot be used.
You can override the choice with set(m, 'solver', '...').
2.9.3. Loose commitment and stochastic replanning
These are set up as two-state Markov-switching problems in which a parameter
called commitment indicates the active mode (1 = commitment, 0 =
discretion). For loose commitment (constant probability, linear-quadratic),
add a two-state chain and @no_u_turn = true:
@optimization_problem[@no_u_turn = true]{ @objective = pi_c^2 + y^2, @discount = beta }
@parameters(looseCommit, 2) commitment
@parameters looseCommit_tp_1_2, looseCommit_tp_2_1
(the chain name is arbitrary; the chain must have exactly two states; you choose
which state has commitment = 1). For stochastic replanning (possibly
time-varying probability, nonlinear), use @no_u_turn = false and let the
transition probabilities be endogenous or exogenous:
@optimization_problem[@no_u_turn = false]{ @objective = pi_c^2 + y^2, @discount = beta }
@parameters(stochrepl, 2) commitment
Again, the model-language reference has the full details (including how to
constrain looseCommit_tp_1_2 = 1 - looseCommit_tp_2_1 for estimation).
2.9.4. Non-cooperative games: OLE vs MPE
When the @optimization_problem block declares more than one policymaker
(see the Multiple Policymakers section of
Description for the model-file
syntax), RISE supports two non-cooperative equilibrium concepts:
Open-loop equilibrium (OLE) – the historical default. Each player optimises against the opponent’s path. The forward-shadow row of every player’s first-order condition is the standard forward-multiplier term
beta * lambda(t+1) * f_3.Markov-perfect equilibrium (MPE) – each player optimises against the opponent’s policy function. The forward-shadow row picks up an additional cross-player chain-rule term that captures how the opponent’s instrument responds to the predetermined state. For a single-player problem the cross-chain is empty and MPE coincides with OLE exactly (Proposition 3 of the implementation note).
You select the equilibrium concept at solve time:
m = solve(m, 'solve_policy_equilibrium', 'OLE'); % default
m = solve(m, 'solve_policy_equilibrium', 'MPE');
The option is case-insensitive. There is no @ole / @mpe model-file
syntax: the model file declares the game (one objective per player, each with
its own @instrument, @order, @discount and @objective); the
equilibrium concept is chosen on the model object before solving, in the same
spirit as solve_policy_type selects commitment vs discretion. The two
options are independent: all four combinations
(solve_policy_type, solve_policy_equilibrium) in {ramsey, discretion} x {OLE, MPE}
are supported in principle.
Note
The MPE option only changes the FOC for multi-player problems, and only
on the rows corresponding to predetermined state variables (variables
that appear at {t-1} in the model). For single-player models, or on FOC
rows for non-state variables (instruments, current-period jumps), the MPE
contribution is zero and the FOC reduces to OLE term-by-term.
Note
Internally MPE is implemented as a small symbolic addition to each
relevant FOC row – no extra endogenous variables are declared, the
augmented system has the same size as the OLE-only system, and at
solve_policy_equilibrium='OLE' (the default) RISE produces
byte-identical results to releases that did not have the option.
Restrictions
Stochastic-replanning models reject the option. Loose-commitment and stochastic-replanning models are managed through the
commitmentswitching parameter and a Markov chain.set(m, 'solve_policy_equilibrium', ...)errors when the model is a stochastic-replanning model. These model classes retain their existing OLE-equivalent behaviour.``@no_u_turn = true`` makes the MPE addition a no-op. The
@no_u_turn = trueoption asks RISE to skip the entire discretion-derivative pipeline (thef1942machinery) in exchange for a cheaper parse. The MPE cross-chain is built from that same machinery, so under@no_u_turn = truethe cross-chain contribution silently degenerates to zero and the FOC is the ordinary OLE FOC. This is intentional:@no_u_turn = trueis the option that asks for “no discretion derivatives at all,” which is inconsistent with the MPE concept. If you want MPE, leave@no_u_turnat its default (false).Single-player consistency. With one player, MPE = OLE by construction. Toggling the option has no observable effect.
Stackelberg. The Stackelberg cascade is handled correctly without any user action. The follower’s set of differentiation variables already includes the leader’s instruments and multipliers (this is part of the existing optimal-policy pipeline), so the cross-chain is correctly built over the same-level Nash opponents only.
The mathematical derivation, the equivalence with an alternative formulation
that introduces the value-function gradient as an explicit endogenous state,
and the audit trail are recorded in the toolbox repository under
development/SpecificThings/mpe_ole_implementation/.
Nash commitment fragility (Tatiana)
The smallest fixture that exercises every
(policy, solve_policy_type, solve_policy_equilibrium) combination is
Tatiana’s monetary-fiscal game, distributed with the regression suite at
rise-stable-tests/models/dsge/optimal_policy/non-cooperative games/small example refactored/.
The model file tatiana.rs declares four policy branches behind a
rise_flags switch – cooperative, nash, M_leader,
F_leader – without pinning commitment or the equilibrium concept, so the
{ramsey, discretion} x {OLE, MPE} cross-product is selectable on the model
object. This yields a 4 x 2 x 2 = 16-cell cube, locked in by
test_tatiana_policy_equilibria.m in the same folder.
The model. Tatiana is a three-equation linearized monetary-fiscal model
with five endogenous variables – output gap X, inflation PI, the
policy rate I, a fiscal instrument TAU, and government debt B –
driven by three shocks (demand E_D, cost-push E_PI, fiscal
E_TAU):
% IS curve
X{t} = X{t+1} - (1/sigma)*(I{t} - PI{t+1}) + alpha*B{t} + E_D{t};
% Phillips curve
PI{t} = beta*PI{t+1} + kappa*X{t} + kappa_tau*TAU{t} + E_PI{t};
% Fiscal constraint
B{t} = (1/beta)*B{t-1} + (tau/beta)*X{t} + (chi/beta)*PI{t} ...
- chi*I{t} + TAU{t} + E_TAU{t};
Two policymakers can act on this block. A monetary authority chooses
I to minimize rho_pi*PI^2 + omega_x*X^2 + omega_i*I^2 (inflation
weight, output-gap weight, interest-rate smoothing) and a fiscal
authority chooses TAU to minimize
PI^2 + omega_x*X^2 + omega_tau*TAU^2 (inflation weight, output-gap
weight, tax smoothing); both discount at beta. The four policy
branches differ in how the two players interact:
cooperative– a single planner with objectiverho_pi*PI^2 + omega_x*X^2 + omega_i*I^2, picking bothIandTAUjointly (the cooperative objective is the monetary loss, so the comparison against the Nash branch is apples-to-apples on the monetary side).nash– both authorities are@order = 1and choose simultaneously.M_leader– Stackelberg with Monetary as the@order = 1leader and Fiscal as the@order = 2follower.F_leader– Stackelberg with Fiscal as the@order = 1leader and Monetary as the@order = 2follower.
The standard calibration (used by the regression test and by the worked
example in development/profiling/mpe_vs_ole_for_tatiana.zip) is
beta = 0.99, sigma = 1.0, kappa = 0.1, kappa_tau = 0.05,
alpha = 0.2, chi = 0.3, tau = 0.1, rho_pi = 1.5,
omega_x = 0.5, omega_i = 0.1, omega_tau = 0.2.
At the standard calibration the cube partitions as follows:
Cooperative + {ramsey, discretion} + {OLE, MPE} – all four cells solve; MPE = OLE in each (single player, cross-chain empty).
M_leader / F_leader + {ramsey, discretion} + {OLE, MPE} – all eight Stackelberg cells solve; MPE = OLE in each (one player per Stackelberg level, same-level cross-chain empty).
Nash + discretion + {OLE, MPE} – both cells solve; this is the only branch where MPE != OLE, because two players at the same Nash level activate the cross-chain.
Nash + ramsey + {OLE, MPE} – both cells return retcode 24 (“Explosive solution”) at this calibration. This is a property of the simultaneous-commitment fixed point, not a toolbox bug.
Why MPE = OLE except in Nash + discretion. As noted under Stackelberg
above, the MPE cross-chain only fires for same-level Nash opponents.
Cooperative cells have a single player, so the cross-chain is empty by
Proposition 3 and the FOC reduces to OLE term-by-term. The Stackelberg cases
have exactly one player per level (Monetary or Fiscal alone at @order=1,
the other alone at @order=2), so the same-level cross-chain is again
empty and the MPE addition is zero on every FOC row. Only the Nash branch
puts two players at the same level; at this calibration that branch solves
only under discretion, which is therefore the lone cell in which the OLE and
MPE solutions are observably different.
Why Nash + commitment is explosive. The simultaneous-commitment fixed
point requires Monetary and Fiscal best responses to be mutually consistent
under full commitment to the entire forward path. With finite weights on the
two instruments and finite cross-elasticities (kappa_tau, chi,
alpha), the combined system’s stable manifold can collapse below the
number of predetermined states and the linear perturbation returns
retcode 24 – explosive solution. The toolbox is healthy: Stackelberg
commitment stabilizes the same model because the leader internalizes the
follower’s reaction, and Nash discretion stabilizes it because
period-by-period reoptimization strips the forward-multiplier dynamics that
drive the instability. The same calibration that explodes under
nash + ramsey solves cleanly under M_leader + ramsey,
F_leader + ramsey, nash + discretion, and every cooperative cell.
Workflow. The policy branch is a parse-time choice (via rise_flags);
commitment vs discretion is a post-parse choice (via solve_policy_type);
OLE vs MPE is a solve-time choice (via solve_policy_equilibrium):
% parse-time: pick the policy branch
m = rise('tatiana', 'rise_flags', {'policy', 'nash'});
% ^^^^^^ or cooperative / M_leader / F_leader
% post-parse: pick commitment vs discretion
m = set(m, 'parameters', params, ...
'solve_policy_type', 'discretion'); % or 'ramsey'
% solve-time: pick the equilibrium concept
[m, retcode] = solve(m, 'solve_policy_equilibrium', 'MPE'); % or 'OLE'
Note
If a multi-player calibration of your own returns retcode 24 under
nash + ramsey, this is the canonical Tatiana fragility, not a
toolbox bug. Before reporting an issue, verify that Stackelberg
commitment (M_leader or F_leader + ramsey) and Nash
discretion both solve at the same parameters – if they do, the
simultaneous-commitment fixed point is the unstable one and the model
needs either a leader-follower structure or discretion.
2.9.5. Welfare and comparing policies
Once a model with an optimization-problem block is solved, evaluate the
planner’s value with calculate_loss:
[welf, retcode] = calculate_loss(m, lossstr); % unconditional
[welf, retcode] = calculate_loss(m, lossstr, shocks_db); % conditional
where lossstr is the loss/objective expression (use the same expression as
in @objective). To compare commitment vs discretion vs loose commitment,
build the corresponding model objects (e.g. via set(m,'solve_policy_type',...)
or by varying the loose-commitment probability) and compare their losses, or
their impulse responses (irf) and simulated paths (simulate /
stoch_simul). For the trade-off between volatilities as a preference weight
varies, use frontier (see Optimal (optimized) simple rules).
2.9.6. Worked example: Ramsey optimal capital taxation
This example – the optimal-policy problem from Kamenik’s Dynare++ tutorial –
is a Ramsey (full-commitment) capital-taxation problem. A planner chooses the
capital tax TAU (whose revenue finances a public good G) to maximise
utility from private consumption C and the public good G, subject to the
competitive-equilibrium constraints: capital accumulation, the public-good
identity G = TAU*alpha*K^alpha, an AR(1) technology Z, and the
representative agent’s Euler equation. The model file is:
@endogenous C G K TAU Z
@exogenous EPS
@parameters eta beta alpha delta phi a rho
@optimization_problem{@objective = C^(1-eta)/(1-eta) + a*G^(1-phi)/(1-phi), @discount = beta}
@model
K = (1-delta)*K{-1} + (exp(Z{-1})*K{-1}^alpha - C{-1} - G{-1});
G = TAU*alpha*K^alpha;
Z = rho*Z{-1} + EPS;
C^(-eta) = beta*C{+1}^(-eta)*(1-delta + exp(Z{+1})*alpha*K{+1}^(alpha-1)*(1-alpha*TAU{+1}));
@parameterization
eta = 2; beta = 0.99; alpha = 0.3; delta = 0.10;
phi = 2.5; a = 0.1; rho = 0.7;
Note that the @model block lists only the constraints (the competitive
equilibrium); RISE forms the planner’s first-order conditions and Lagrange
multipliers automatically from the @optimization_problem block.
Because the model is nonlinear, the steady state must be found from a positive
initial guess (the default start is where C^(-eta) is not finite). Supply
one through sstate_bounds ([init, lower, upper] per variable) and solve
under full commitment ('ramsey'):
m = rise('ramsey_tax','max_deriv_order',3); % allow a higher-order solve later
b = struct();
b.C = [0.77, 0.01, inf];
b.G = [0.31, 0.01, inf];
b.K = [2.90, 0.01, inf];
b.TAU = [0.75, 0.001, 0.99];
[mc, rc] = solve(m, 'solve_policy_type', 'ramsey', 'sstate_bounds', b);
The Ramsey steady state – capital taxed at about 76% to finance the public good, since there is no other revenue source – is:
C = 0.7744 G = 0.3119 K = 2.9004 TAU = 0.7554 Z = 0
Impulse responses to the technology shock follow from irf(mc). Because the
problem is nonlinear, a higher-order approximation is available for the same
model by passing solve_order (max_deriv_order above must be at least as
large):
mc3 = solve(m, 'solve_policy_type', 'ramsey', 'sstate_bounds', b, 'solve_order', 3);
Note
Discretion and this model. Solving the same problem under
'discretion' fails, and this is an economic feature rather than a
numerical accident: discretionary capital taxation is the classic
time-inconsistency / capital-levy problem – a planner re-optimising each
period wants to expropriate already-accumulated capital, so the
discretionary steady state is degenerate. A clean commitment-vs-discretion
comparison therefore needs a stabilisation model (e.g. a small New
Keynesian one), as below.
2.9.7. Worked example: comparing policy types in a New Keynesian model
To compare commitment, discretion and loose commitment / stochastic
replanning we need a stabilisation problem in which discretion is well-behaved
(unlike the capital-tax model above). A 3-equation New Keynesian model serves:
an IS curve, a Phillips curve with a cost-push shock, and a quadratic planner
loss in inflation and the output gap, with the nominal rate R as the
instrument:
@endogenous PAI Y R U
@exogenous E_PAI
@parameters beta kappa sigma lambda rho_u sig_u
@optimization_problem{@objective = -0.5*(PAI^2 + lambda*Y^2), @discount = beta}
@model
Y = Y{+1} - (1/sigma)*(R - PAI{+1}); % IS curve (R is the instrument)
PAI = beta*PAI{+1} + kappa*Y + U; % Phillips curve
U = rho_u*U{-1} + sig_u*E_PAI; % cost-push shock
@parameterization
beta = 0.99; kappa = 0.1; sigma = 1; lambda = 0.25;
rho_u = 0.5; sig_u = 1;
Commitment and discretion are selected on this model through
solve_policy_type:
m = rise('nk_opt');
mc = solve(m, 'solve_policy_type', 'ramsey'); % commitment
md = solve(m, 'solve_policy_type', 'discretion'); % discretion
Loose commitment / stochastic replanning is obtained by making the degree of
commitment a switching parameter: declare a two-state looseCommit chain with
a commitment parameter equal to 1 in the “commit” state and 0 in the
“reoptimise” state, and give it transition probabilities. RISE then uses its
loose-commitment solver automatically:
@parameters looseCommit_tp_1_2 looseCommit_tp_2_1
@parameters(looseCommit,2) commitment
...
@parameterization
commitment(looseCommit,1) = 1; % commit
commitment(looseCommit,2) = 0; % reoptimise
looseCommit_tp_1_2 = 0.1;
looseCommit_tp_2_1 = 0.9;
mlc = solve(rise('nk_opt_lc'));
Rank the policies by the unconditional planner loss (lower is better),
evaluated with calculate_loss on the loss expression:
loss = '0.5*(PAI^2 + lambda*Y^2)';
Lc = calculate_loss(mc, loss);
Ld = calculate_loss(md, loss);
Llc = calculate_loss(mlc, loss);
which reproduces the textbook ordering – commitment dominates loose commitment, which dominates discretion:
policy |
unconditional loss |
|---|---|
commitment |
205.5 |
loose commitment / replanning |
235.2 |
discretion |
358.3 |
Impulse responses to the cost-push shock follow from irf(mc),
irf(md), irf(mlc) and can be overlaid to see how commitment damps
inflation more aggressively than discretion. The model files and a runnable
driver are kept under
models/dsge/optimal_policy/worked_examples/nk_monetary_policy in the test
repository.