Models

This section contains the API documentation for economic model classes and building blocks.

Core Model Classes

DBlock

class skagent.model.DBlock(name='', description='', shocks=<factory>, dynamics=<factory>, reward=<factory>)

Bases: Block

Represents a ‘block’ of model behavior. It prioritizes a representation of the dynamics of the block. Control variables are designated by the appropriate dynamic rule.

Parameters:
  • shocks (Mapping(str, Distribution or tuple)) –

    A mapping from variable names to Distribution objects, representing exogenous shocks.

    Optionally, the mapping can be to tuples of Distribution constructors and dictionary of input arguments. In this case, the dictionary can map argument names to numbers, or to strings. The strings are parsed as mathematical expressions and evaluated in the scope of a calibration dictionary.

  • dynamics (Mapping(str, str or callable)) – A dictionary mapping variable names to mathematical expressions. These expressions can be simple functions, in which case the argument names should match the variable inputs. Or these can be strings, which are parsed into functions.

  • reward (Mapping(str, str)) – A dictionary mapping variable names to agent role labels. The variable name will almost always appear in ‘dynamics’. The agent role indicates which agent views the variable as a reward to optimize.

  • name (str)

  • description (str)

calc_reward(vals)

Computes the reward for a given set of variable values

construct_shocks(calibration, rng=None)

Constructs all shocks given calibration. This method mutates the DBlock.

Parameters:
  • calibration (dict) – Calibration parameters for shock construction

  • rng (np.random.Generator, optional) – Random number generator to use for distribution construction

deep_replace(name=None, description=None, shocks=None, dynamics=None, reward=None)

Creates a deep copy of the block with new shocks, dynamics, and rewards dictionaries. These dictionaries will have updated values based on the inputs.

description: str = ''
discretize(disc_params)

Returns a new DBlock which is a copy of this one, but with shock discretized.

dynamics: dict
get_arrival_value_function(disc_params, dr, continuation)

Returns an arrival value function, which is the value of the states upon arrival into the block.

This involves taking an expectation over shocks (which must first be discretized), a decision rule, and a continuation value function.)

get_decision_value_function(dr, continuation)

Given a decision rule and a continuation value function, return a function for the value at the decision step/tac, after the shock have been realized.

## TODO: it would be better to systematize these value functions per block ## better, then construct them with ‘partial’ methods

get_dynamics()
get_shocks()
get_state_rule_value_function_from_continuation(continuation, screen=False)

Given a continuation value function, returns a state-rule value function: the value for each state and decision rule. This value includes both the reward for executing the rule ‘this period’, and the continuation value of the resulting states.

get_vars()

Returns the variables that are created/modified by the Block. Does not include variables that are only used as arguments to the dynamics. TODO: Get a way to find these.

iter_dblocks()

A DBlock is its own leaf.

name: str = ''
reward: dict
shocks: dict
transition(pre, dr, screen=False, until=None, fix=None)

RBlock

class skagent.model.RBlock(name='', description='', blocks=<factory>)

Bases: Block

A recursive block.

Parameters:
  • ...

  • name (str)

  • description (str)

  • blocks (List[Block])

blocks: List[Block]
construct_shocks(calibration, rng=None)

Construct all shocks given a calibration dictionary.

Parameters:
  • calibration (dict) – Calibration parameters for shock construction

  • rng (np.random.Generator, optional) – Random number generator to use for distribution construction

description: str = ''
discretize(disc_params)

Recursively discretizes all the blocks. It replaces any DBlocks with new blocks with discretized shocks.

get_controls()

Returns only the Control variables from the Block dynamics.

get_dynamics()
get_shocks()
get_vars()
iter_dblocks()

Iterate over all DBlock leaves in this RBlock tree.

name: str = ''
property reward

The reward attributions for all subblocks.

Control

class skagent.model.Control(iset, lower_bound=None, upper_bound=None, agent=None)

Bases: object

Used to designate a variable that is a control variable.

Parameters:
  • iset (list of str) – The labels of the variables that are in the information set of this control.

  • lower_bound (function) – An ‘equation function’ which evaluates to the lower bound of the control variable.

  • upper_bound (function) – An ‘equation function’ which evaluates to the upper bound of the control variable.

  • agent (str) – A label identifying the agent role to which this control is attributed.

Aggregate

class skagent.model.Aggregate(dist)

Bases: object

Used to designate a shock as an aggregate shock. If so designated, draws from the shock will be scalar rather than array valued.

Parameters:

dist (Distribution)

Model Utilities

Simulation Dynamics

skagent.model.simulate_dynamics(dynamics, pre, dr)

From the beginning-of-period state (pre), follow the dynamics, including any decision rules, to compute the end-of-period state.

Parameters:

dynamics (Mapping[str, Callable]) – Maps variable names to functions from variables to values. Can include Controls ## TODO: Make collection of equations into a named type

preMapping[str, Any]

Bound values for all variables that must be known before beginning the period’s dynamics.

drMapping[str, Callable]

Decision rules for all the Control variables in the dynamics.

Parameters:

Shock Construction

skagent.model.construct_shocks(shock_data, scope, rng=None)

Returns a dictionary from shock labels to Distributions.

When the corresponding value in shock_data contains a distribution constructor and input information, any symbolic expressions used in the inputs are evaluated in the provided scope.

Parameters:

shock_data (Mapping(str, Distribution or tuple)) –

A mapping from variable names to Distribution objects, representing exogenous shocks.

Optionally, the mapping can be to tuples of Distribution constructors and dictionary of input arguments. In this case, the dictionary can map argument names to numbers, or to strings. The strings are parsed as mathematical expressions and evaluated in the scope of a calibration dictionary.

scope: dict(str, values)

Variables assigned to numerical values. The scope in which expressions will be evaluated

rng: np.random.Generator, optional

Random number generator to pass to distribution constructors. If provided, distributions created from tuples will use this RNG.

Discretized Shock Distribution

skagent.model.discretized_shock_dstn(shocks, disc_params)

Discretizes a collection of independent shocks and combines them into one DiscreteDistributionLabeled.

Shocks are discretized only if they have a corresponding element of disc_params defined.

Parameters:
  • shocks (dict of Distribution) – A dictionary of Distributions, representing independent exogenous shocks.

  • disc_params (dict of dict) – A dictionary of dictionaries with arguments to Distribution.discretize. Keys of this dictionary should be shared with the shocks argument.

Predefined Models

The skagent.models subpackage contains predefined economic models:

Consumer Models

Benchmarks

Complete Catalogue of Analytically Solvable Consumption-Savings Models

This module implements the exhaustive collection of discrete-time consumption-savings dynamic programming problems for which the literature has succeeded in writing down true closed-form policies. The catalogue is complete as of June 13, 2025, to the best of our knowledge: no other analytically solvable DP variants are currently known.

THEORETICAL FOUNDATION

An entry qualifies for inclusion ONLY if: (i) The problem is a bona-fide dynamic programming problem (ii) The optimal c_t (and any other control) can be written in closed form with no recursive objects left implicit

Global Notation (Discrete Time Only)

t ∈ {0,1,2,…} : period index A_t : end-of-period assets (risk-free bond, gross return R=1+r>1) y_t : non-capital income c_t : consumption m_t := A_t + h_t : cash-on-hand; h_t = E_t[∑_{s≥0} R^{-(s+1)} y_{t+s}] u(c) : period utility TVC : lim_{T→∞} E_0[β^T u’(c_T) A_T] = 0

class skagent.models.benchmarks.U6HabitSolver(calibration)

Exact Riccati solution for quadratic utility with habit formation

Note: The current implementation uses a simplified solution that is exact for the standard calibration (quad_a=1.0, quad_b=0.5). For general parameters, a full 2×2 algebraic Riccati equation solver would be needed.

skagent.models.benchmarks.d1_analytical_lifetime_reward(initial_wealth, discount_factor, interest_rate)

Analytical lifetime reward for D-1: Two-period log utility model.

With optimal policy c1 = W/(1+β), c2 = βRW/(1+β): Lifetime reward = ln(c1) + β*ln(c2) = ln(W/(1+β)) + β*ln(βRW/(1+β)) = (1+β)*ln(W) + β*ln(βR) - (1+β)*ln(1+β)

Parameters:
Return type:

float

skagent.models.benchmarks.d1_analytical_policy(calibration)

D-1: c1 = W/(1+β), c2 = β*R*W/(1+β)

Parameters:

calibration (Dict[str, Any])

Return type:

Callable

skagent.models.benchmarks.d2_analytical_lifetime_reward(initial_wealth, discount_factor, interest_rate, time_horizon)

Analytical lifetime reward for D-2: Finite horizon log utility.

Forward simulation that exactly matches the D-2 model implementation.

Parameters:
Return type:

float

skagent.models.benchmarks.d2_analytical_policy(calibration)

D-2: c_t = (1-β)/(1-β^(T-t+1)) * W_t (remaining horizon formula)

Parameters:

calibration (Dict[str, Any])

Return type:

Callable

skagent.models.benchmarks.d3_analytical_lifetime_reward(cash_on_hand, discount_factor, interest_rate, risk_aversion)

Analytical lifetime reward for D-3: Infinite horizon CRRA.

With optimal policy c = κ*m where κ = (R - (βR)^(1/σ))/R: V(m) = κ^(1-σ)/(1-σ) * m^(1-σ) / (1 - β*(βR)^((1-σ)/σ))

Parameters:
Return type:

float

skagent.models.benchmarks.d3_analytical_policy(calibration)

D-3: c_t = κ*m_t where κ = (R - (βR)^(1/σ))/R

Parameters:

calibration (Dict[str, Any])

Return type:

Callable

skagent.models.benchmarks.d4_analytical_policy(calibration)

D-4: c_t = κ_s*m_t where κ_s = 1 - (sβR)^(1/σ)/R

Parameters:

calibration (Dict[str, Any])

Return type:

Callable

skagent.models.benchmarks.euler_equation_test(model_id, test_points=100)

Test Euler equation satisfaction for stochastic analytical solutions

Parameters:
  • model_id (str)

  • test_points (int)

Return type:

Dict[str, Any]

skagent.models.benchmarks.get_analytical_lifetime_reward(model_id, *args, **kwargs)

Get analytical lifetime reward for a benchmark model.

Parameters:
  • model_id (str) – Model identifier (D-1, D-2, D-3, etc.)

  • *args – Arguments to pass to the specific analytical function

  • **kwargs – Arguments to pass to the specific analytical function

Returns:

Analytical lifetime reward value

Return type:

float

skagent.models.benchmarks.get_analytical_policy(model_id)

Get analytical policy function by model ID

Parameters:

model_id (str)

Return type:

Callable

skagent.models.benchmarks.get_benchmark_calibration(model_id)

Get benchmark calibration by model ID

Parameters:

model_id (str)

Return type:

Dict[str, Any]

skagent.models.benchmarks.get_benchmark_model(model_id)

Get benchmark model by ID (D-1, D-2, D-3, D-4, U-1, U-2, U-3, U-4, U-5, U-6)

Parameters:

model_id (str)

Return type:

DBlock

skagent.models.benchmarks.get_custom_validation(model_id)

Get custom validation function for model (if it has one)

Parameters:

model_id (str)

Return type:

Optional[Callable]

skagent.models.benchmarks.get_test_states(model_id, test_points=10)

Get test states for model validation by model ID

Parameters:
  • model_id (str)

  • test_points (int)

Return type:

Dict[str, Tensor]

skagent.models.benchmarks.list_benchmark_models()

List all 10 analytically solvable discrete-time models from the catalogue

Return type:

Dict[str, str]

skagent.models.benchmarks.u1_analytical_policy(calibration)

U-1: Martingale consumption c_t = c_{t-1} when β*R = 1

Parameters:

calibration (Dict[str, Any])

Return type:

Callable

skagent.models.benchmarks.u2_analytical_policy(calibration)

U-2: c_t = (r/R)*A_t + y_t - (1/r)*[log(βR)/γ + γ*σ²/2]

Parameters:

calibration (Dict[str, Any])

Return type:

Callable

skagent.models.benchmarks.u3_analytical_policy(calibration)

U-3: Martingale consumption with time-varying β_t = 1/R_t

Parameters:

calibration (Dict[str, Any])

Return type:

Callable

skagent.models.benchmarks.u4_analytical_policy(calibration)

U-4: c_t = (1-β)*[A_t + H_t] where H_t is human wealth

Parameters:

calibration (Dict[str, Any])

Return type:

Callable

skagent.models.benchmarks.u5_analytical_policy(calibration)

U-5: Collapses to D-3 CRRA rule when θ=γ

Parameters:

calibration (Dict[str, Any])

Return type:

Callable

skagent.models.benchmarks.u6_analytical_policy(calibration)

U-6: Linear state-feedback c_t = φ1*y_t + φ2*h_{t-1} (exact Riccati solution)

Parameters:

calibration (Dict[str, Any])

Return type:

Callable

skagent.models.benchmarks.validate_analytical_solution(model_id, test_points=10, tolerance=1e-08)

Validate analytical solution satisfies optimality conditions and budget constraints

Parameters:
Return type:

Dict[str, Any]

Perfect Foresight Models


This page is under construction. Content will be added as the API develops.