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>(herehousehold_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_problemblock.
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
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 |
|---|---|
|
the derived model as a column cell array of code lines (the canonical artifact) |
|
write the derived model to a |
|
print the derived model (optionally one agent’s block) |
1.2.8. API reference
Call |
Meaning |
|---|---|
|
create a builder |
|
declare endogenous variables (inline |
|
declare shocks |
|
declare parameters; |
|
declare a Markov chain |
|
open an agent’s problem (returns a fluent handle) |
|
the period objective |
|
the choice (control) variables |
|
named constraints, equality or inequality (multiplier
|
|
market-clearing / identity equations |
|
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.