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.

Regimes

A regime is a phase of life with its own utility function, states, actions, and constraints. Models have at least one non-terminal regime and one terminal regime.

For how functions and dependency wiring work inside a regime, see Write Economics, Not Glue Code. This page covers the regime-level features:

  • Regime anatomy — what each field does

  • Terminal vs non-terminal regimes

  • Transitions — regime and state (overview; full reference in Transitions)

  • The active predicate

from lcm import AgeGrid, LogSpacedGrid, Model
from lcm_examples.tiny import (
    RegimeId,
    retirement,
    working_life,
)

Regime Anatomy

A Regime is defined by these fields:

FieldTypePurpose
transitionCallable, MarkovTransition, or NoneNext-regime transition function. None marks a terminal regime. Wrap in MarkovTransition for stochastic transitions.
activeCallable[[float], bool]Age-based predicate — when the regime is active (default: always).
statesdict[str, Grid]State variable grids — pure outcome-space definitions.
state_transitionsdict[str, Callable | MarkovTransition | None]How states evolve over time. None for fixed states.
actionsdict[str, Grid]Choice variables with grids.
functionsdict[str, Callable]Must include "utility"; can include auxiliary functions.
constraintsdict[str, Callable]Feasibility constraints on state-action combinations.
descriptionstrOptional description string.

Terminal vs Non-Terminal Regimes

  • Terminal regime: transition=None. The value function equals the utility function directly — there is no continuation value.

  • Non-terminal regime: transition is a callable. pylcm auto-injects an aggregation function HH that combines utility with the discounted continuation value:

H(ut,Vt+1,β)=ut+βVt+1H(u_t, V_{t+1}, \beta) = u_t + \beta \, V_{t+1}

This is the default aggregation. pylcm supports user-defined aggregation functions for non-standard objectives (e.g., Epstein-Zin preferences).

A terminal regime provides the boundary condition for the dynamic programming problem — it is the last period’s value function. Note that the last period can vary across agents, for example through stochastic mortality. A terminal regime does not mean the agent stays in this regime forever (that would be an absorbing regime, which is non-terminal with a self-loop transition).

Common uses:

  • Simple retirement: one period where the agent consumes all remaining wealth

  • Death: terminal utility is zero or determined by a bequest function — see the three-regime example in Write Economics

RETIREMENT_AGE = 65

# The retirement regime from lcm_examples.tiny is already a terminal regime
print("Terminal?", retirement.terminal)
print("Utility function:", retirement.functions["utility"].__name__)
Terminal? True
Utility function: utility_retirement

Transitions

Regimes define two kinds of transitions:

  • Regime transitions (transition field) — which regime does the agent enter next period? Can be deterministic (return a regime ID) or stochastic (return probabilities via MarkovTransition).

  • State transitions (state_transitions field) — how do individual state variables evolve? Supports deterministic functions, fixed states (None), stochastic transitions (MarkovTransition), and target-regime-dependent transitions.

See Transitions for the full reference with examples.

The active Predicate

The active field is a callable that takes an age (float) and returns True if the regime can be occupied at that age. This is useful when regimes are only relevant during certain life stages:

# Working life: active before retirement age
working_life = Regime(
    transition=next_regime,
    active=lambda age: age < 65,
    ...
)

# Retirement: active from retirement age onward
retirement = Regime(
    transition=None,
    active=lambda age: age >= 65,
    ...
)

pylcm uses active to skip regimes that cannot be reached at a given age during the backward induction. If active is not provided, it defaults to lambda _age: True (always active).

Note that active does not prevent an agent from entering a regime — it tells the solver which regimes to compute value functions for at each age. The regime transition function is what actually determines regime assignment.

Example: Assembling Regimes into a Model

A minimal two-regime model to show the full assembly. For the economic functions and DAG wiring, see Write Economics. For model construction details, see Defining Models.

# Customize the imported working_life regime with simpler grids and functions
working_life_simple = working_life.replace(
    actions={
        **working_life.actions,
        "consumption": LogSpacedGrid(start=0.5, stop=50, n_points=50),
    },
)

age_grid = AgeGrid(start=25, stop=65, step="20Y")

model = Model(
    regimes={
        "working_life": working_life_simple,
        "retirement": retirement,
    },
    ages=age_grid,
    regime_id_class=RegimeId,
)

print("Regimes:", list(model.regimes.keys()))
print("Periods:", model.n_periods)
Regimes: ['working_life', 'retirement']
Periods: 3