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 with rise_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: only loose_commitment is 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 commitment switching 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 = true option asks RISE to skip the entire discretion-derivative pipeline (the f1942 machinery) in exchange for a cheaper parse. The MPE cross-chain is built from that same machinery, so under @no_u_turn = true the cross-chain contribution silently degenerates to zero and the FOC is the ordinary OLE FOC. This is intentional: @no_u_turn = true is the option that asks for “no discretion derivatives at all,” which is inconsistent with the MPE concept. If you want MPE, leave @no_u_turn at 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 objective rho_pi*PI^2 + omega_x*X^2 + omega_i*I^2, picking both I and TAU jointly (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 = 1 and choose simultaneously.

  • M_leader – Stackelberg with Monetary as the @order = 1 leader and Fiscal as the @order = 2 follower.

  • F_leader – Stackelberg with Fiscal as the @order = 1 leader and Monetary as the @order = 2 follower.

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.