Skip to content

Time Indices in Dolo Function Compilation

Overview

Time indices ([t], [t+1], [t-1]) in dolo equations serve as a declarative specification of which time periods' data a function needs. During compilation, these indices are transformed into positional arguments in the generated functions. This document explains how this transformation works and why it matters for understanding dolo's architecture.

Time Index Meanings

  • [t-1] or -1: Previous period (past)
  • [t] or 0: Current period (present)
  • [t+1] or 1: Next period (future)

The Compilation Process

1. Starting Point: YAML Equations

equations:
    transition:
        - k[t] = (1-delta)*k[t-1] + i[t-1]

    arbitrage:
        - 1 - beta*(c[t]/c[t+1])^(sigma)*(1-delta+rk[t+1])

2. Recipe-Driven Signature Generation

The recipes.yaml file defines which time periods each equation type needs:

arbitrage:
    eqs:
        - ['states', 0, 's']      # Current states (t)
        - ['controls', 0, 'x']    # Current controls (t)
        - ['states', 1, 'S']      # Future states (t+1)
        - ['controls', 1, 'X']    # Future controls (t+1)
        - ['parameters', 0, 'p']  # Parameters (timeless)

transition:
    eqs:
        - ['states', -1, 's']     # Past states (t-1)
        - ['controls', -1, 'x']   # Past controls (t-1)
        - ['states', 0, 'S']      # Current states (t) - output

3. The Transformation Pipeline

Step 1: Parse with Time Information

# Original equation
"k[t] = (1-delta)*k[t-1] + i[t-1]"

# Parsed AST preserves timing
AST: Assignment(
    Variable('k', 0),
    Add(
        Mult(Sub(1, 'delta'), Variable('k', -1)),
        Variable('i', -1)
    )
)

Step 2: Remove Timing for Calibration

# In model.py, calibration uses remove_timing()
"k = (1-delta)*k + i"  # For steady-state solving

Step 3: Apply Time Shifts

# factories.py line 117
eqs = [time_shift(eq, tshift) for eq in eqs]

# This transforms variable names based on context:
# If processing for time t:
"k = (1-delta)*k__m1 + i__m1"

Step 4: Map to Function Arguments

# Based on recipes.yaml specification
arguments = {
    's': ['k'],        # From ['states', -1, 's']
    'x': ['i'],        # From ['controls', -1, 'x']
    'S': ['k'],        # From ['states', 0, 'S'] (output)
    'p': ['delta']     # From ['parameters', 0, 'p']
}

Step 5: Generated Function

def transition(s, x, p):
    # Inside, k[t-1] → s[0] (first element of states array)
    # i[t-1] → x[0] (first element of controls array)
    # k[t] → output
    k = (1-p.delta)*s[0] + x[0]
    return np.array([k])

Key Insight: Time Indices as Argument Mapping

Time indices don't exist in the compiled function. They're a compile-time directive that determines:

  1. Which arrays the function receives as arguments
  2. The order of these arguments
  3. How variables map to array positions

Example: Arbitrage Function

# YAML equation
1 - beta*(c[t]/c[t+1])^(sigma)*(1-delta+rk[t+1])

Compiles to:

def arbitrage(m_t, s_t, x_t, m_tp1, s_tp1, x_tp1, p):
    # c[t] comes from x_t array
    # c[t+1] comes from x_tp1 array
    # rk[t+1] is computed from s_tp1, x_tp1

    c = x_t[0]      # Current consumption
    c_next = x_tp1[0]  # Future consumption
    rk_next = compute_rk(s_tp1, x_tp1, p)

    return 1 - p.beta*(c/c_next)**(p.sigma)*(1-p.delta+rk_next)

The tshift Parameter

The get_factory() function accepts a tshift parameter that shifts all time indices:

# Normal (tshift=0): k[t-1] → past, k[t] → current
# Shifted (tshift=1): k[t-1] → current, k[t] → future

This is useful for: - Creating lagged versions of functions - Adjusting reference frames for different algorithms - Generating derivative functions at different time points

Implications for ADC Models

Understanding time indices is crucial for the ADC extension because:

  1. ADC models are timeless at the stage level
  2. Time indices create cross-stage coupling
  3. Removing time shifts (setting tshift=0 always) is key to modularity

In the proposed ADC extension:

# Check for ADC equations
adc_equations = ['g_av', 'g_ve', 'q_function', 'foc_adc']
if eq_type in adc_equations:
    tshift = 0  # Force no time shift

This ensures ADC transition functions remain pure state-to-state mappings without temporal coupling.

Summary

Time indices in dolo serve as a declarative specification that gets compiled away:

  1. Before compilation: k[t-1] indicates "I need past k"
  2. During compilation: Maps to specific function argument
  3. After compilation: Just args[0][0] (first state, first variable)

They're not time travel - they're instructions for the compiler about data organization. This understanding is essential for extending dolo to support timeless, modular representations like ADC.