1.2. Deriving models with rise.microfound

1.2.1. Write the economics, not the algebra

rise.microfound lets you author a DSGE model the way you would write it in a paper: declare the atoms and their roles, state each agent’s optimization problem (period objective, choice variables, named constraints), and RISE derives the first-order conditions symbolically and generates a plain RISE model. It does not own or augment a model object – its output is ordinary model code you can read, solve, and edit. Calibration and solving remain the responsibility of dsge_model.

The builder is a MATLAB handle object with a fluent (chained) interface. It is a model definition tool only: it never takes parameter values and has no solve method.

1.2.2. A first example: the RBC model

p = rise.microfound("RBC");
p.endogenous("c","n","k","w","r","Y","A");
p.exogenous("eps");
p.parameter("beta","delta","psi","eta","alpha","rho");

p.agent("household", discount="beta") ...
    .maximize("log(c) - psi*n^(1+eta)/(1+eta)") ...
    .choose("c","n","k") ...
    .subject_to(budget="c + k = w*n + (1-delta+r)*k{-1}");

p.equilibrium( ...
    production="Y = A*k{-1}^alpha*n^(1-alpha)", ...
    labdemand ="w = (1-alpha)*A*k{-1}^alpha*n^(-alpha)", ...
    capdemand ="r = alpha*A*k{-1}^(alpha-1)*n^(1-alpha)", ...
    tfp       ="A = A{-1}^rho*exp(eps)");

The object can be handed to dsge_model three equivalent ways:

m = dsge_model(p);           % (1) directly
m = dsge_model(code(p));     % (2) via the generated cell array of code lines
rs(p,'rbc.rs'); m = dsge_model('rbc.rs');   % (3) via a written .rs file

Parameter values are passed to dsge_model at solve time, as a structure:

params = struct('beta',0.99,'delta',0.025,'psi',1,'eta',1,'alpha',0.36,'rho',0.95);
m = solve(m, 'parameters', params);
% Number of solutions: 1
% steady state: c=2.555, n=0.928, k=35.24, w=2.371, r=0.0351, Y=3.436, A=1

1.2.3. What is generated

code(p) returns the derived model as a column cell array of code lines; rs(p,file) writes it to a .rs file, and show(p) prints it. For the RBC above the generated model is:

@endogenous c n k w r Y A household_lambda_budget
@exogenous eps
@parameters beta delta psi eta alpha rho
@model
    % production
    Y = A*k{-1}^alpha*n^(1-alpha);
    % labdemand
    w = (1-alpha)*A*k{-1}^alpha*n^(-alpha);
    % capdemand
    r = alpha*A*k{-1}^(alpha-1)*n^(1-alpha);
    % tfp
    A = A{-1}^rho*exp(eps);
    % household: budget
    (c + k)-(w*n + (1-delta+r)*k{-1});
    % household: FOC wrt c
    (1)/(c)+household_lambda_budget*(1);
    % household: FOC wrt n
    -((((rise.engine.sym.real_power(n,1+eta,1))*(psi))*(1+eta))/((1+eta)^(2)))+household_lambda_budget*(-(w));
    % household: FOC wrt k
    household_lambda_budget*(1)+(beta)*household_lambda_budget{+1}*(-(1-(delta)+r{+1}));

Three conventions to note:

  • Lagrange multipliers are added automatically as endogenous variables, named <agent>_lambda_<constraint-label> (here household_lambda_budget). The labour-supply and Euler equations are the derived FOCs; the budget constraint is emitted as a residual.

  • Equation tags. Every derived equation is preceded by a comment identifying its origin: % <agent>: <constraint-label> for a constraint, % <agent>: FOC wrt <control> for a first-order condition, and the label you gave for an equilibrium equation. These make the generated file self-documenting.

  • No optimization block, nothing hidden. The output is an ordinary RISE model: equilibrium identities, the derived FOCs, the agents’ constraints, and the shock processes. There is no @optimization_problem block.

Note

Steady state. microfound does not compute or require the analytical steady state – providing it would mean hand-deriving exactly the algebra the tool automates. dsge_model forms the steady-state system from the derived FOCs and solves it numerically. You may pass p.steady_state(var=expr,...) with a rough initial guess (a single seed per variable) to help the solver; it is refined, not taken as the solution, and can often be omitted. This mirrors gEcon, which likewise derives and numerically solves the steady-state equations.

1.2.4. Multiple agents

An economy can have several agents; each agent(...) block is differentiated independently and contributes its own FOCs and multipliers. A firm that chooses capital and labour to maximise profit, for instance, is written as another agent:

p.agent("firm") ...
    .maximize("A*k{-1}^alpha*n^(1-alpha) - w*n - r*k{-1}") ...
    .choose("k","n");

The gEcon example corpus (consumer + firm problems, adjustment costs, capital laws of motion, two-sector and two-country variants) is reproduced this way; see the validation note below.

1.2.5. Regime switching

Regime switching enters through switching parameters – and nothing else in how the agent’s problem is written changes. Declare a Markov chain, mark the parameters that switch on it, and the derived FOCs become the correct, probability-weighted switching first-order conditions automatically. This is a capability gEcon does not have.

p = rise.microfound("Switching-preferences RBC");
p.endogenous("c","n","k","w","r","Y","A");
p.exogenous("eps");
p.chain("pref", 2);                       % a 2-state Markov chain named "pref"
p.parameter("beta","delta","eta","alpha","rho");
p.parameter("pref_tp_1_2","pref_tp_2_1"); % chain transition probabilities
p.parameter("psi", switches_on="pref");   % labour disutility switches on "pref"

p.agent("household", discount="beta") ...
    .maximize("log(c) - psi*n^(1+eta)/(1+eta)") ...
    .choose("c","n","k") ...
    .subject_to(budget="c + k = w*n + (1-delta+r)*k{-1}");
% ... equilibrium block unchanged ...

The switching parameter is emitted under a chain-scoped header, @parameters(pref,2) psi, and the labour FOC references psi (which now switches). Switching values and transition probabilities are supplied at solve time with the underscore convention <param>_<chain>_<state> and <chain>_tp_<i>_<j>:

params = struct('beta',0.99,'delta',0.025,'eta',1,'alpha',0.36,'rho',0.95, ...
                'psi_pref_1',1, 'psi_pref_2',3, 'pref_tp_1_2',0.1, 'pref_tp_2_1',0.1);
m = solve(dsge_model(p), 'parameters', params);
% Number of solutions: 1
% regime 1 (psi=1):  n=0.928  c=2.555  k=35.24
% regime 2 (psi=3):  n=0.536  c=1.475  k=20.35

The steady state is solved per regime from the derived switching FOCs: higher labour disutility (psi=3) yields lower hours and capital, as expected.

1.2.6. Inequality constraints (Kuhn-Tucker)

An inequality is written exactly like an equality – nothing else about the agent’s problem changes:

p.agent("household", discount="beta") ...
    .maximize("log(c) - psi*n^(1+eta)/(1+eta)") ...
    .choose("c","n","k","I") ...
    .subject_to( ...
        budget  = "c + I = w*n + r*k{-1}", ...
        capital = "k = (1-delta)*k{-1} + I", ...
        irrev   = "I >= 0");

microfound normalizes the constraint to its \(g \le 0\) side, attaches the multiplier <agent>_lambda_<label> to the Lagrangian exactly as for an equality (so it appears in the FOC of every control the constraint touches), and emits the complementarity condition as a kink:

% household: irrev (KKT)
max(household_lambda_irrev,(0)-(I));

The Lagrangian is formed as \(L = U + \lambda g\), so the derived multiplier is the negative of the textbook one. The Kuhn-Tucker conditions \(\lambda \le 0\), \(g \le 0\), \(\lambda g = 0\) are then exactly

\[\max(\lambda, g) = 0,\]

and that max is emitted directly. RISE recognizes the kink at parse time and enforces it through its occasionally-binding-constraint machinery – exactly under perfect foresight, and by anticipated fudge shocks (or the OccBin paradigm) in a stochastic solve. See Occasionally-binding constraints.

Both directions are accepted: A >= B and A <= B normalize to the same \(g \le 0\) form, and strict inequalities are treated as their non-strict counterparts (the distinction is measure-zero for the complementarity row).

A constraint that never binds leaves the model exactly as it was. The irreversibility above is slack in steady state – \(I = \delta k > 0\) – and the model reproduces the plain RBC steady state (c=2.55527, n=0.927729, k=35.2437) with a multiplier of zero, which is the point of an occasionally-binding constraint: it bites only in bad states. examples/microfound/rbc_irreversible.m is the runnable driver.

When it does bind

Start that economy with twice its steady-state capital, hit it with a \(-30\%\) technology shock, and solve under perfect foresight (where the kink is enforced exactly rather than approximated). The unconstrained economy would disinvest for 12 periods; the constrained one cannot, and sits at \(I = 0\) for 13. Three things are then true of the derived model, and all three are checked by testMicrofoundKKTDynamics:

  • every path – the multipliers included – matches the same economy with its first-order conditions derived by hand, to \(5 \times 10^{-13}\);

  • on the binding arc \(I = 0\) makes the capital law of motion an identity, so capital must decay at exactly \(1-\delta\); it does, to 0.000e+00;

  • complementarity holds period by period: \(\lambda \le 0\), \(g \le 0\) and \(\max|\lambda g| = 3 \times 10^{-15}\) across the whole path, with the multiplier exactly zero wherever the constraint is slack and bounded away from zero wherever it binds.

The multiplier is largest in magnitude when the constraint first bites and climbs monotonically back to zero at the period investment lifts off – the shadow value of being allowed to disinvest, decaying as the desire to do so does. The tutorial ModelShapes/microfound_kkt runs the whole experiment and plots it.

1.2.7. Output methods

Method

Result

code(p)

the derived model as a column cell array of code lines (the canonical artifact)

rs(p,file)

write the derived model to a .rs file

show(p) / show(p,"agent")

print the derived model (optionally one agent’s block)

1.2.8. API reference

Call

Meaning

p = rise.microfound("Title")

create a builder

p.endogenous("x", "y: a description", ...)

declare endogenous variables (inline "name: description" optional)

p.exogenous("eps", ...) / p.shock(...)

declare shocks

p.parameter("beta", ..., switches_on="chain")

declare parameters; switches_on marks a switching parameter

p.chain("name", nstates)

declare a Markov chain

p.agent("name", discount="beta")

open an agent’s problem (returns a fluent handle)

.maximize("U") / .minimize("L")

the period objective

.choose("c","n","k")

the choice (control) variables

.subject_to(label="L = R", ...)

named constraints, equality or inequality (multiplier <agent>_lambda_<label>); an inequality yields the KKT row max(lambda, g) = 0

p.equilibrium(label="eqn", ...)

market-clearing / identity equations

p.steady_state(var="expr", ...)

optional rough initial guess for the numerical steady state

1.2.9. Scope and roadmap

Optimal policy. microfound derives the first-order conditions of private (competitive) agents – it assumes rational expectations with full commitment and does not solve a planner’s (Ramsey) problem. For optimal policy, use RISE’s dedicated optimal-policy facilities (the @optimization_problem planner block); the two share the same underlying FOC engine (rise.engine.foc). A natural workflow is to generate the competitive economy with microfound and add a planner block for the Ramsey analysis.

Structural regime switching. Switching enters through switching parameters (above). The state= argument on .maximize(...) and .subject_to(...) – a regime-specific objective or constraint form – is accepted and stored, but does not yet change the generated model; only the state-0 objective and the full constraint list are emitted.

Calibrated parameters from steady-state targets. gEcon-style back-solving of a parameter from a steady-state restriction (r[ss]*Kd[ss] = 0.36*Y[ss] -> alpha) has no equivalent yet; supply such parameters directly.

LaTeX output. latex(p,file) is declared on the class but is not wired (it raises rise:microfound:notWired); it is a fast-follow on the derivation core.

Heterogeneous agents (HANK). The heterogeneity vocabulary is present in the API – axis(name, points, ...), individual(...), aggregate(...), aggregation(...), and over= on an agent – so that a HANK layer can emit @heterogeneity_axis / @endogenous(individual) / @Agg constructs. In the current release this vocabulary is stored but not yet derived: authoring an individual’s problem over a wealth grid is a planned (phase-2) capability, not a functional one today. Representative-agent and regime-switching models are the supported cases.

1.2.10. Validation

The representative-agent derivation is validated by re-authoring nine gEcon example models in microfound – basic RBC, RBC with installation costs, capacity utilisation, habit formation, monopolistic competition (with a household stochastic discount factor), two sectors, home production, time-to-build, and two countries with risk sharing. All derive and solve, and the two-sector model reproduces gEcon’s own derived steady state. See examples/microfound/ in the toolbox for runnable drivers.

The four shipped drivers (rbc, rbc_switching_prefs, gecon_rbc_ic, teaser_household) and the regression test models/dsge/microfound/testMicrofoundJensen are re-run against the toolbox on each release; the steady-state values quoted on this page come from that run.