1.1. Model file language

A RISE model lives in a plain-text file with the extension .rs. The file is read by the RISE parser when you call dsge_model('mymodel.rs') and is converted into a model object on which the engines (solve, estimate, filter, forecast, …) operate. This chapter is the reference for everything you can write in that file.

The model file describes the model: which variables exist, which parameters they depend on, and what equations they satisfy. It does not carry calibration, priors, or solve-time choices – those live in companion MATLAB files or as options on the relevant calls. See What does not go in the model file below.

1.1.1. Block keywords at a glance

A .rs file is a sequence of blocks, each introduced by an @ keyword. The blocks may appear in any order; the parser collects them and links them together.

Keyword

Purpose

@endogenous

Declare endogenous variables.

@exogenous

Declare exogenous innovations (shocks).

@parameters

Declare parameters whose value does not switch.

@parameters(chain,N[,names...])

Declare parameters that switch across N regimes of a Markov chain.

@observables

Declare the variables observed in estimation / filtering.

@model

The list of equations. Required.

@steady_state_model

Closed-form steady state (optional but recommended for non-trivial models).

@transition_functions

Endogenous (time-varying) transition probabilities – one equation per off-diagonal entry per chain.

@optimization_problem

Replace one or more equations with the first-order conditions of a planner’s problem (optimal policy).

@outside_optimization{vars}

Equations solved with the model that are not constraints of the optimal-policy players, who take vars as given (see Optimal policy); @outside_optimization(home){vars} exempts only the players named; without optimal policy, ordinary model equations.

@epilogue

Equations evaluated after solve and filter, e.g. for diagnostics or to derive variables not needed in the solution.

A complete minimal example – a three-equation New Keynesian model with two shocks – appears at the end of this chapter.

1.1.2. Declarations

Endogenous variables

Variables determined inside the model:

@endogenous y pi i

You can attach a comment to a variable; comments survive in the solution table and in plots:

@endogenous Y "Output", C "Consumption", PAI "Inflation",
            R "Interest rate (net)"

Variables in logs. Tag a variable to be interpreted in logs by adding (log) to the declaration:

@endogenous(log) Y C
@endogenous PAI R

The shorter form is one line per group, but you may also mix them in the same block:

@endogenous Y "Output" C "Consumption"
@endogenous(log) MU "Technology growth"

A log variable is solved for, and approximated, in logs, but its name always means its level: in @model, @outside_optimization, @transition_functions and @epilogue alike, Y stands for the level of output, and RISE substitutes exp of the log it stores. Write every block in levels.

Exogenous variables (shocks)

White-noise innovations – one per Gaussian shock you want to feed into the model:

@exogenous eps_m "Monetary shock", eps_a "Technology shock"

Parameters

Non-switching parameters:

@parameters beta sigma kappa phi_pi phi_y rho_g
            std_eps_m std_eps_g

Switching parameters

A parameter that takes different values in different regimes of a Markov chain is declared with @parameters(chain, N) where N is the number of states of the chain:

@parameters(mon,2) phi_pi
@parameters(fis,2) gamma_b

This declares two chains – mon and fis – each with two states. phi_pi takes two values; gamma_b takes two values. The model has 2 x 2 = 4 composite regimes.

You can label the regimes inline:

@parameters(zlb,2,"normal times","lower bound") gam

The labels appear in solution tables and plots.

The chain is introduced by its first appearance as the first argument of @parameters(chain,N); the user names it there. The transition probabilities are declared either as ordinary parameters (constant probabilities) or as equations in a @transition_functions block (endogenous / time-varying probabilities); see Markov chains below.

Observables

The variables you observe in the data when filtering or estimating:

@observables y pi i

There must be at least as many shocks as observables, otherwise the predicted observation covariance becomes singular (“stochastic singularity”).

1.1.3. The @model block

The @model block contains the equations. Each equation ends with ;. Equations may span several lines.

Time indexing

Variables are indexed by time. Three equivalent notations are available; the {...} form is recommended for readability:

x{t}    x{t-1}    x{t+1}        % explicit time index
x       x{-1}     x{+1}         % t implicit, offsets explicit
x       x(-1)     x(+1)         % parenthesis form

A steady-state value of a variable is written {stst}:

R{stst}        % steady-state value of R

The function forms stst(R) and stst(R{+1}) are also accepted. In a model with a balanced growth path, the steady state of the lead of a growing variable moves with the trend: see Steady states of growing variables in the dynamic equations.

Equation labels

A quoted string immediately before an equation labels it. The label appears in error messages, in printed solutions, and in historical decompositions:

"Dynamic IS curve"
x{t} = x{t+1} - (1/sigma)*(i{t} - pi{t+1}) + g{t};

"Phillips curve"
pi{t} = beta*pi{t+1} + kappa*x{t};

Auxiliary definitions

A line that begins with # inside @model introduces a short-hand. It is substituted into subsequent equations at parse time:

@model

    # rho_theta = rho_e;
    # phi      = (thetass-1)/psi;
    # sig_theta = sig_e*phi;

    log(THETA) = (1-rho_theta)*log(thetass)
               + rho_theta*log(THETA{-1})
               + sig_theta*EPS_THETA;

Definitions can reference each other in declaration order and may use any parameter or steady-state value already declared.

Steady-state-form companion equations

A dynamic equation may be followed by # and a simpler steady-state form, separated from the rest of the equation by a # token and terminated by the same ;. RISE uses the steady-state form when computing the deterministic steady state, which often converges much faster than evaluating the full dynamic equation at the steady state:

"Euler equation"
-B*LAMBDA + betad*B{+1}*LAMBDA{+1}*(1+R)/PAI{+1} = 0
#
1+R = PAI*exp(MU)/betad
;

Both forms have to be mathematically consistent at the steady state.

Comments

% starts a line comment. Block comments are not supported.

1.1.4. Steady state

A model file describes the model; the steady state belongs in a separate function. The reason is the same as the reason calibration belongs in a separate *_params.m file: a model and its steady state are two different things, and either can change without the other. Conflating them inside the .rs file is bad practice.

@steady_state_model block (in-file alternative)

The steady state may also be written directly inside the model file in a @steady_state_model block:

@steady_state_model

    A = 1;
    Y = (thetass-1)/thetass*(zss-beta*gam)/(zss-gam);
    C = Y;
    ...

The block is evaluated, not solved – the right-hand side may reference parameters and earlier steady-state expressions in declaration order. The separate steady-state file is the recommended pattern; the in-file block is available when keeping everything in one file is the user’s preference.

No steady-state specification

When neither a sstate_file nor a @steady_state_model is provided, RISE attempts a numerical steady-state solve from a default initial guess of zero. This works for log-linearized models whose steady state is zero by construction.

1.1.5. Markov chains

RISE handles Markov-switching as a first-class concept. A chain is introduced by writing parameters that switch on it (via @parameters(chain,N)) and by declaring its transition probabilities – either as constant parameters or as equations in a @transition_functions block.

Constant transition probabilities

For a chain mon with N states, the off-diagonal transition probabilities are declared as ordinary parameters with the name pattern mon_tp_<i>_<j>:

@parameters mon_tp_1_2 mon_tp_2_1

@parameters(mon,2) phi_pi

The diagonal probabilities mon_tp_1_1 and mon_tp_2_2 are inferred (each row of the transition matrix must sum to 1) and must not be declared explicitly.

Calibrate them like any other parameter:

p.mon_tp_1_2 = 0.05;
p.mon_tp_2_1 = 0.05;
p.phi_pi_mon_1 = 2.5;
p.phi_pi_mon_2 = 0.7;
m = set(m, parameters = p);

Endogenous transition probabilities

When the probabilities depend on endogenous variables (TVTP), declare them as equations in a @transition_functions block:

@parameters p_min "transition-prob floor",
            p_max "transition-prob ceiling",
            theta_zlb "sensitivity to log(B)"

@parameters(zlb,2,"normal times","lower bound") gam

@transition_functions

    "Probability normal -> bound: rises as preference demand falls"
    zlb_tp_1_2 = p_min + (p_max - p_min) / (1 + B^theta_zlb);

    "Probability bound -> normal: rises as preference demand recovers"
    zlb_tp_2_1 = p_min + (p_max - p_min) / (1 + B^(-theta_zlb));

The right-hand side may reference any endogenous variable, parameter, or steady-state value. Keep the probabilities strictly interior (0 < p < 1) – a transition-function that touches zero or one makes the chain absorbing and is rarely intended.

Occasionally-binding constraints

An OBC is a Markov chain whose switching is determined endogenously by the binding state of the constraint, not by exogenous probabilities. Declare the chain in the usual way:

@parameters(zlb,2) bind

with the constraint encoded in the model:

i{t} = bind*i_floor + (1-bind)*i_taylor{t};

and tell solve to honor the constraint at solve time (see Modeling -> Solving). The transition probabilities of an OBC chain must be zero (the switching is endogenous, not stochastic); solve enforces this.

1.1.6. Optimal policy

When you want one equation in the model to be replaced by the first-order conditions of a planner’s problem – i.e., to choose an instrument optimally rather than according to a rule – declare an @optimization_problem block.

Single-player problem

@optimization_problem{
    @objective = -0.5*(pi^2 + lambda_y*y^2 + lambda_i*i^2),
    @discount  = beta
}

@model
    % no Taylor rule -- i is the planner's instrument, chosen
    % to minimize the discounted sum of -objective
    y{t}  = y{t+1} - (1/sigma)*(i{t} - pi{t+1}) + g{t};
    pi{t} = beta*pi{t+1} + kappa*y{t};
    ...

The instrument is whichever endogenous variable does not appear on the left-hand side of any equation in @model. The choice between commitment and discretion is made at solve time:

m = solve(m, solve_policy_type = "ramsey");        % commitment
m = solve(m, solve_policy_type = "discretion");

There is no @commitment parameter and no @-prefixed marker in the model file – the solve choice is an option, not a model property.

Multi-player problem (Nash, Stackelberg)

Two players with separate objectives and instruments:

@optimization_problem[@no_u_turn=false]{
    Monetary:
        @order      = 1
        @discount   = beta
        @objective  = -0.5*(pi^2 + lambda_y*y^2 + lambda_i*i^2)
        @instrument = i ;
    Fiscal:
        @order      = 2
        @discount   = beta
        @objective  = -0.5*(tau^2 + lambda_b*b^2)
        @instrument = tau ;
}

When all players have the same @order, the game is Nash. When the orders differ, it is a Stackelberg cascade with the lower @order as the leader.

1.1.7. Epilogue

Equations that are not needed to solve the model but should be evaluated on the paths the model produces – diagnostics, accounting identities, output transformations – live in an @epilogue block:

@epilogue

    "Annualised inflation"
    pi_ann = 400*pi;

    "Ex-post real interest rate"
    r_real = i - pi{+1};

    "Inflation over the last four quarters"
    pi_4q = pi + pi{-1} + pi{-2} + pi{-3};

Epilogue equations may reference any endogenous variable but they do not affect the solution. They are evaluated after the fact, which keeps the dynamic system small. An entry may be labelled like an equation of the model, and may use other entries, in any order (RISE sorts them; a cycle is an error).

Where the series appear

Each entry becomes a series, computed on the path the output holds, in the output of

  • simulate, and so of forecast, which simulates. A forecast’s path starts at the last date of history, so an entry with a lag has no value on that date;

  • perfect_foresight;

  • counterfactual, on the counterfactual and on the actual path;

  • brute_force_simulate;

  • filter: the smoothed, filtered and updated variables.

irf has none. An impulse response is a deviation, and an entry with a constant or a nonlinearity is not the response of that entry: pi_ann above would come out right, 400*(pi-1) would not. For the response of an entry, simulate with and without the shock and take the difference of the entry.

A parameter in an entry has the value of the parameterization that produced the path; with several parameterizations, each page of the output has its own. A switching parameter follows the regime path of a simulation. The other outputs carry no regime path, and there it takes its value in the first regime.

With measurement errors the observables in the output of simulate are complex, the error in the imaginary part. An entry is a function of the model’s variables and is evaluated on the real parts.

The entries of @transition_functions are evaluated in the same way, so each transition probability has a series of its own, under its name (zlb_tp_1_2), in all these outputs: the function evaluated on the path at each date. See Time-varying transition probabilities for the probabilities the simulation itself used.

Leads and lags in the epilogue

An epilogue entry is computed from a path RISE has already computed, so a lead or a lag in it is a literal shift of that path, not an expectation. In r_real above, pi{+1} is the inflation that followed; in @model the same expression is the inflation agents expect. Hence:

  • Leads and lags may be of any order, on endogenous and exogenous variables and on other epilogue entries. They add nothing to the model: none of the auxiliary variables @model gets for its leads and lags beyond one and its dated shocks (LEAD_, LAG_, JLEAD_, AUX_) is created for them, and the model is the same with or without the epilogue.

  • A series has no value where its path runs out: a lead of k leaves the last k periods empty, a lag of k the first k.

  • Every time notation of the model block works: x{t+2}, x{+2} and x(+2) are the same series, and so for lags.

  • A parameter or a definition has no path. One with a lead or a lag, such as beta{+1}, is refused when the model is read, with the equation and the name (identifier RISE:epilogue:timeIndexedParameter); beta{t} is beta.

  • An exogenous variable is available only on a path that carries it. The outputs of simulate, forecast and perfect_foresight carry every exogenous variable. The variables filter returns and the outputs of counterfactual and brute_force_simulate carry an exogenous variable that is observed, never a shock: an entry that uses a shock is left out of them, and so is an entry that uses that one.

For an expectation, write the variable into @model instead. The tutorial ModelShapes/epilogue_leads_and_lags writes I - PI{+1} in both blocks: the expected real rate in @model, the realized one in @epilogue.

1.1.8. What does not go in the model file

The model file describes the model. By the modern convention several things are kept out:

Numerical parameter values

Calibration lives in a companion MATLAB file (e.g. mymodel_params.m) that returns a struct of parameter values:

function p = mymodel_params()
    p = struct();
    p.beta  = 0.99;
    p.sigma = 1.0;
    p.kappa = 0.30;
    ...
end

and is bound to the model with set:

m = set(m, parameters = mymodel_params());

This keeps model and calibration on independent change cycles. The .rs file may also carry a @parameterization block; the separate *_params.m file is the recommended pattern.

Steady-state values

Same reasoning, applied to the steady state. The steady-state function (a plain MATLAB file, name of your choice) is bound via sstate_file:

m = set(m, sstate_file = 'my_steady_state');

The @steady_state_model block inside the model file is also supported when keeping everything in one file is the user’s preference. See Steady state above.

Solve-time choices

Choices about how the model is solved – commitment vs discretion, perturbation strategy, solver, solve paradigm, … are options on the solve call, not declarations in the model file:

m = solve(m, solver = '+mn', solve_policy_type = "ramsey");

The model file does not change when you switch from commitment to discretion.

Estimation priors

Priors live in the same companion _params.m (or in a dedicated _priors.m) as a separate output of the same function:

function [p, priors] = mymodel_est_params()
    p = struct();
    p.beta = 0.99;
    ...
    priors = struct();
    priors.kappa = {0.30, 0.10, 0.60, 'beta_pdf(0.9)', 1e-6, 0.999};
    ...
end

and are bound with:

m = set(m, parameters = p, estim_priors = priors);

Priors may also be declared inside the .rs file’s @parameterization block; the separate *_est_params.m file is the recommended pattern, since priors are an estimation choice rather than a model property.