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: