Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Write Economics, Not Glue Code

pylcm lets you write economic equations that look like the math in your papers. You define small functions — one for utility, one for earnings, one for the budget constraint — and pylcm wires them together automatically. This page shows why that matters and how it works.

Named functions are also the seam used by specialized solvers. Keep economically meaningful resources and post-decision quantities as named DAG nodes. When a solver needs a boundary or comparison itself—not only its computed value—use the corresponding structured declaration. See Choosing a solver.

The Model on Paper

Consider a consumption-savings model where the agent chooses labor supply dt∈{work,retire}d_t \in \{\text{work}, \text{retire}\} and consumption ctc_t each period. An economist would write it like this:

Vt(wt)=max⁡dt, ct{u(ct,dt)+β Vt+1(wt+1)}V_t(w_t) = \max_{d_t,\, c_t} \left\{ u(c_t, d_t) + \beta\, V_{t+1}(w_{t+1}) \right\}
u(ct,dt)=ln⁡ct−ϕ⋅1[dt=work]yt=1[dt=work]⋅wˉwt+1=(1+r)(wt−ct)+ytct≤wt\begin{align} u(c_t, d_t) &= \ln c_t - \phi \cdot \mathbf{1}[d_t = \text{work}] \\ y_t &= \mathbf{1}[d_t = \text{work}] \cdot \bar{w} \\ w_{t+1} &= (1 + r)(w_t - c_t) + y_t \\ c_t &\leq w_t \end{align}

Notice the dependency structure implicit in the math:

  • Utility depends on whether the agent works

  • Next-period wealth depends on labor income

  • Labor income depends on whether the agent works

When you write these equations in a paper, the evaluation order is obvious to the reader. In code, someone has to enforce it.

The Dependency Graph

Those implicit dependencies form a directed acyclic graph (DAG):

Reading the DAG: an arrow from A to B means “B needs the value of A”. So next_wealth needs labor_income, which needs is_working, which needs the action labor_supply. The question is: who manages this wiring?

The Traditional Approach

In a traditional implementation, you manage the call order and pass intermediate results by hand:

This works, but has real costs:

  • You manage the call order. Swap steps 1 and 2 and you get a bug.

  • You pass intermediate results. Add a new auxiliary variable and you rewire the entire function.

  • The economics is buried in plumbing. The actual equations are the short lines; most of the code is scaffolding.

  • It doesn’t scale. With five states, three auxiliary variables, and multiple regimes, this monolith becomes unreadable.

The pylcm Way

In pylcm, each equation from the paper becomes its own function. The function’s name is the variable it computes, and its parameters are the variables it needs:

Compare next_wealth above with the paper equation:

wt+1=(1+r)(wt−ct)+ytw_{t+1} = (1 + r)(w_t - c_t) + y_t

The code reads almost identically. And notice that labor_income appears as both a function name and a parameter of next_wealth. pylcm (via dags) resolves this: it calls labor_income(...) first and feeds the result into next_wealth. You never write labor_income = labor_income(is_working, wage) yourself.

Assembling the Regime

The functions are collected into functions and constraints dicts. The dict key is the name pylcm uses for wiring — so utility_working is registered under the key "utility" (which every regime must have):

State transitions live on the Regime via the state_transitions dict. This separates outcome-space definitions (grids) from dynamics (transitions). Regime transitions live on the model’s edges: a source with several destinations at some age carries a Transition whose law picks one, and a regime without outgoing edges, like dead, is terminal:

pylcm inspects the signatures of every registered function and constraint, builds the dependency graph, and determines that is_working must run before labor_income, which must run before next_wealth. You describe the economics; pylcm handles the plumbing.

Each function above is the same closure for every period the regime is solved. See Age-specialized functions and grids to let a function’s closure vary with age instead — for example, a tax-transfer system pinned to a policy date that changes as a cohort ages through calendar time.

For more on regimes — terminal vs non-terminal, stochastic transitions — see Regimes; for age-dependent laws with ByAge, see Age-indexed regimes.

What You Don’t Have to Do

Compare this with the traditional approach from above:

  • No manual call ordering. pylcm topologically sorts the DAG for you.

  • No passing intermediate results. The output of is_working is automatically fed to labor_income and utility_working.

  • No shared state dict. Each function declares exactly what it needs via its signature.

  • Adding a new auxiliary function is trivial. Add it to the functions dict and use its name as a parameter in other functions — done.

The One Rule: One Function, One Output

The key mental-model difference from traditional code: each pylcm function returns exactly one thing. In traditional code you might compute several values in one block; in pylcm you split them into separate functions, each returning a single result. This is what makes the DAG work — each node produces one named value that other nodes can reference by name.

Example contrast:

This is actually closer to how economists write equations in papers — each equation defines one variable.

To see how to parametrise and solve this model, continue to Parameters and Solving and Simulating. For a complete end-to-end walkthrough, see A Tiny Example. To let a function’s closure or a continuous state’s grid vary with age, see Age-specialized functions and grids.