Skip to content

Function Signatures and Usage Guide

Understanding Function Inputs and Outputs

The Key Insight

ALL inputs to dolo functions are arrays, including both: 1. Variables (states, controls, exogenous) - the values you're solving for 2. Parameters (β, γ, etc.) - fixed calibrated values

Function Signature Pattern

Single Variable Model

For a model with one of each variable type:

# Model symbols
symbols:
    exogenous: [y]
    states: [w]
    controls: [c]
    expectations: [mr]
    parameters: [β, γ, σ, ρ, r]

# Function signature (all functions)
func(y, w, c, mr, params)
# where params = [β, γ, σ, ρ, r]

Multiple Variable Model

For a model with multiple variables per category:

# Model symbols
symbols:
    exogenous: [eps_z, eps_h]
    states: [k, h, z]
    controls: [c, l, i]
    parameters: [α, β, δ, ...]

# Function signature
func(exo, states, controls, params)
# where:
#   exo = [eps_z, eps_h]     - length 2
#   states = [k, h, z]        - length 3
#   controls = [c, l, i]      - length 3
#   params = [α, β, δ, ...]   - fixed length

Array Shapes and Vectorization

Single Point Evaluation

import numpy as np

# Single observation (each array has 1 element)
y = np.array([0.0])          # Shape: (1,)
w = np.array([2.0])          # Shape: (1,)
c = np.array([0.9])          # Shape: (1,)
mr = np.array([0.5])         # Shape: (1,)
params = np.array([0.96, 4.0, 0.1, 0.0, 1.0])  # Shape: (5,)

# Call function
result = model.functions['auxiliary_direct_egm'](y, w, c, mr, params)
# result shape: (1,)

Vectorized Evaluation (Multiple Points)

# N = 1000 grid points
N = 1000

# Variables: Each is an array of N values
y = np.zeros(N)                        # Shape: (1000,)
w = np.linspace(0.1, 10.0, N)         # Shape: (1000,)
c = w * 0.6                           # Shape: (1000,)
mr = np.ones(N) * 0.5                 # Shape: (1000,)

# Parameters: SAME for all N points
params = np.array([0.96, 4.0, 0.1, 0.0, 1.0])  # Shape: (5,)

# Call function - evaluates at all N points simultaneously
result = model.functions['auxiliary_direct_egm'](y, w, c, mr, params)
# result shape: (1000,)

Multiple Variables Per Category

# Model with 3 state variables, 2 controls
N = 500  # grid points

# States: k, h, z
states = np.column_stack([
    np.linspace(1, 10, N),    # k values
    np.linspace(0.5, 5, N),   # h values
    np.ones(N) * 0.0          # z values
])  # Shape: (500, 3)

# Controls: c, l
controls = np.column_stack([
    np.linspace(0.1, 2, N),   # c values
    np.ones(N) * 0.3          # l values
])  # Shape: (500, 2)

# Parameters stay the same
params = np.array([0.3, 0.96, 0.1, ...])  # Shape: (num_params,)

# Function call
result = func(exo, states, controls, params)
# result shape: (500, num_outputs)

Understanding Input Arguments

What Each Argument Represents

Argument Type Represents When Vectorized
y, w, c, etc. Variable arrays Current values Grid/sample points
params Parameter vector Fixed calibration Same for all points

Variables vs Parameters

Variables - Change across evaluations: - Current wealth w[t] - Consumption choice c[t] - Shock realization y[t]

Parameters - Stay constant: - Discount factor β - Risk aversion γ - Interest rate r

Both are inputs to the function!

Common Function Patterns

1. Transition Function

# Equation: w[t] = exp(y[t]) + (w[t-1] - c[t-1])*r

# Inputs (from recipes.yaml):
# - y[t-1], w[t-1], c[t-1] (past values)
# - y[t] (current shock)
# - parameters

# Usage
w_next = model.functions['transition'](
    y_past, w_past, c_past, y_current, params
)

2. Euler Equation (Arbitrage)

# Equation: β*(c[t+1]/c[t])^(-γ)*r - 1 = 0

# Inputs:
# - Current: y[t], w[t], c[t]
# - Future: y[t+1], w[t+1], c[t+1]
# - parameters

residual = model.functions['arbitrage'](
    y_t, w_t, c_t,
    y_tp1, w_tp1, c_tp1,
    params
)

3. Expectation Function

# Equation: mr[t] = β*(c[t+1])^(-γ)*r

# Inputs: Future values
mr = model.functions['expectation'](
    y_tp1, w_tp1, c_tp1, mr_tp1, params
)

Special Cases

Functions That Don't Use All Inputs

Even if an equation only uses some variables, the function signature includes ALL:

# Equation: a[t] = w[t] - c[t]
# Only uses w and c, but signature still needs all inputs

a = func(y, w, c, mr, params)
# y and mr are ignored internally

This maintains consistency across all functions.

Time-Shifted Variables

Functions may need variables at different times:

# transition needs t-1 values
# arbitrage needs both t and t+1 values
# expectation needs t+1 values

The recipes.yaml file specifies which time periods each function needs.

Practical Usage Examples

Grid Evaluation for Policy Function

# Create wealth grid
w_grid = np.linspace(0.1, 10.0, 200)

# Fixed other values
y = np.zeros(200)
mr = np.ones(200) * 0.5

# Solve for optimal consumption at each wealth level
for i in range(max_iter):
    c = solve_for_c(w_grid, ...)  # Some solver

    # Evaluate Euler equation residual
    residual = model.functions['arbitrage'](
        y, w_grid, c, y_next, w_next, c_next, params
    )

    if np.max(np.abs(residual)) < tol:
        break

Monte Carlo Simulation

T = 1000  # time periods
N = 10000  # sample paths

# Simulate shocks
y_sim = np.random.normal(0, σ, (T, N))

# Initialize
w = np.ones(N) * w0

for t in range(T):
    # Policy function
    c = policy(w)

    # Next period wealth (vectorized over N paths)
    w = model.functions['transition'](
        y_sim[t-1], w_prev, c_prev, y_sim[t], params
    )

Performance Tips

1. Vectorize When Possible

# Bad: Loop over points
results = []
for i in range(1000):
    r = func(y[i:i+1], w[i:i+1], c[i:i+1], mr[i:i+1], params)
    results.append(r)

# Good: Vectorized call
results = func(y, w, c, mr, params)  # 100x faster!

2. Reuse Parameter Array

# Create once
params = np.array([β, γ, σ, ρ, r])

# Use many times
for iteration in range(1000):
    result = func(y, w, c, mr, params)  # Same params

3. Preallocate Arrays

# Preallocate for grid evaluation
N = 1000
y = np.zeros(N)
w = np.linspace(w_min, w_max, N)
c = np.empty(N)  # Will be filled

Common Gotchas

1. Scalar vs Array

# Wrong: Passing scalars
result = func(0.0, 2.0, 0.9, 0.5, params)  # Error!

# Right: Always use arrays
result = func(
    np.array([0.0]),
    np.array([2.0]),
    np.array([0.9]),
    np.array([0.5]),
    params
)

2. Shape Mismatch

# Wrong: Inconsistent lengths
y = np.zeros(100)
w = np.zeros(200)  # Different length!
result = func(y, w, c, mr, params)  # Error!

# Right: All same length
y = np.zeros(100)
w = np.zeros(100)
c = np.zeros(100)
mr = np.zeros(100)

3. Parameter Order

The parameter vector order must match the model's parameter declaration order:

parameters: [β, γ, σ, ρ, r]  # This order!
params = np.array([0.96, 4.0, 0.1, 0.0, 1.0])  # Same order

Next: Practical Examples →