Block Guide

This guide explains how to work with economic models in scikit-agent. You’ll learn about the core modeling concepts, how to build custom models, and how to use predefined models.

Understanding Model Structure

The Block Architecture

scikit-agent uses a “block” architecture where models are composed of building blocks:

  • DBlock (Dynamic Block): Represents a structured environment in which agents act

  • RBlock (Recursive Block): Combines multiple blocks into a more complex block

  • Bellman Block: A period of a dynamic model

These blocks all define the ways that variables change. The relationships between variables are defined in terms of structural equations, which in scikit-agent are represented as functions.

Some variables are reserved as control variables, which are assigned to particular agent roles. Agents can choose a decision rule to decide their action at each of their control variables.

Some variables are reserved as reward variables, which provide the agent utility or incentive.

scikit-agent models normally involve agents who are trying to maximize their reward through their choices.

DBlocks

A DBlock has four main components:

  • Shocks: Variables which are drawn from probabilistic distributions.

  • Dynamics: Variables determined by structural equations, or functions, of other variables

  • Controls: special dynamic variables for which agents decide decision rules.

  • Rewards: special dynamic variables that agents try to optimize

Here is an example of a simple DBlock representing a single stage of a consumption-saving problem:

import math
import skagent as ska
from skagent.distributions import MeanOneLogNormal

# Example: Simple consumption block
consumption_block = ska.DBlock(
    name="consumption_stage",
    # 1. Shocks: Random variables
    shocks={"theta": (MeanOneLogNormal, {"sigma": 0.1})},  # Income shock
    # 2. Dynamics: State transition equations
    dynamics={
        "y": lambda p, theta: p * theta,  # Income = permanent * transitory
        "m": lambda b, y: b + y,  # Market resources = beginning + income
        "c": ska.Control(["m"]),  # Consumption (control variable)
        "a": lambda m, c: m - c,  # Assets = resources - consumption
        "u": lambda c: math.log(c),
    },
    # 3. Rewards: What agents maximize
    reward={"u": "consumer"},  # Utility goes to consumer agent
)

This corresponds to the following mathematical model:

\[\begin{split} \theta &\sim LogNormal(1, 0.1) \\ y &= p \theta \\ m &= b_{-1} + y \\ c &= c(m) \\ a &= m - c \\ u &= log(c) \\ \end{split}\]

Here, the agent can choose its level of consumption \(c\) given an information set \(m\). It receives \(u\) as a reward.

Arrival states

Dynamic equations are interpreted in sequence. Each variable is assigned based on the values of other variables in scope. If a variable is referenced in a dynamic equation before it is assigned, it is an arrival state, or lag variable, which refers to a value that is assigned in some preceding block or time step.

In the example above, \(b_{-1}\) is such a variable.

Arrival states can be provided by a previous block (see RBlocks, below), in a previous time period (see BellmanPeriod), or by initialization data before a simulation.

Control Variables

A Control variable is under the control of some agent. Instead of providing a dynamic equation, the modeler specifies an information set – what information (variables) are available to the agent when they decide this variable’s value.

Constraints

Control variables can be upper and lower bound to values that are themselves functions of state variables.

consumption_control = ska.Control(
    iset=["m", "p"],  # Information set
    lower_bound=lambda: 0.001,  # Minimum consumption
    upper_bound=lambda m: 0.99 * m,  # Maximum consumption
    agent="consumer",  # Agent assignment
)

Calibration

calibration = {
    "CRRA": 2.0,  # Risk aversion
    "DiscFac": 0.96,  # Discount factor
    "Rfree": 1.03,  # Risk-free rate
    "EqP": 0.06,  # Equity premium
    "RiskyStd": 0.20,  # Stock volatility
    "PermGroFac": 1.01,  # Permanent income growth
    "TranShkStd": 0.1,  # Transitory shock std
    "PermShkStd": 0.05,  # Permanent shock std
}

# Apply calibration to construct actual distributions
portfolio_block.construct_shocks(calibration)

String-Based Dynamics

You can define dynamics using string expressions that get parsed automatically:

dynamics = {
    "c": ska.Control(["m"]),
    "u": "c**(1-CRRA)/(1-CRRA)",  # String expression
    "mpc": "CRRA * c**(-CRRA)",  # Marginal propensity to consume
    "a": "m - c",  # Simple arithmetic
}

RBlocks: Composing Blocks

The RBlock is for composing other blocks together.

# Retirement transition block
retirement_block = ska.DBlock(
    name="retirement",
    dynamics={
        "p": lambda p: p * 0.8,  # Retirement income drop
        "retired": lambda: 1,  # Retirement indicator
    },
)

# Life-cycle model
lifecycle_model = ska.RBlock(
    name="lifecycle_model", blocks=[portfolio_block, retirement_block]
)

TODO: Discussion of how the blocks connect – arrival states, again.

Bellman Blocks

TODO: about bellman blocks – how these become MDPs

Model Validation and Inspection

Examining Model Structure

# Get all variables in the model
variables = portfolio_block.get_vars()
print("Model variables:", variables)

# Get control variables
controls = portfolio_block.get_controls()
print("Control variables:", controls)

# Get shock variables
shocks = portfolio_block.get_shocks()
print("Shock variables:", list(shocks.keys()))

Testing Model Dynamics

# Test model transition with simple decision rules
pre_state = {
    "m": 2.0,
    "p": 1.0,
    "a_prev": 1.0,
}

decision_rules = {
    "c": lambda m: 0.8 * m,
    "alpha": lambda a: 0.6,  # 60% in risky asset
}

# Simulate one period
post_state = portfolio_block.transition(pre_state, decision_rules)
print("Post-transition state:", post_state)

Next Steps

Common Patterns

Habit Formation Models

habit_block = ska.DBlock(
    dynamics={
        "c": ska.Control(["m", "h"]),
        "x": lambda c, h: c / h,  # Consumption relative to habit
        "u": lambda x, CRRA: x ** (1 - CRRA) / (1 - CRRA),
        "h": lambda h_prev, c_prev, rho: rho * h_prev + (1 - rho) * c_prev,
    }
)

Durable Goods Models

durables_block = ska.DBlock(
    dynamics={
        "c_nd": ska.Control(["m"]),  # Non-durable consumption
        "i_d": ska.Control(["m", "d"]),  # Durable investment
        "d": lambda d_prev, i_d, delta: (1 - delta) * d_prev + i_d,  # Durable stock
        "c_d": lambda d: d,  # Durable services
        "u": lambda c_nd, c_d, alpha: (c_nd**alpha * c_d ** (1 - alpha)) ** (1 - CRRA)
        / (1 - CRRA),
    }
)

This guide provides the foundation for building and working with economic models in scikit-agent. The block-based architecture provides flexibility while maintaining clear economic interpretation.