shape-runtime 0.3.1

Bytecode compiler, builtins, and runtime infrastructure for Shape
Documentation
/// @module std::core::simulation
/// Core Simulation Module - Multi-Table State Machine Operations
///
/// Provides simulation primitives for event-driven state machines over time series.
/// This module is domain-agnostic - finance, IoT, and other industry-specific
/// simulation wrappers should build on these primitives.
///
/// ## SIMD-First Design Philosophy
///
/// `simulate()` and `simulate_correlated()` are thin sequential layers for state
/// machines. Most computation should happen BEFORE simulate using SIMD primitives:
///
/// ```shape
/// // CORRECT: SIMD-first approach
/// let signals = prices.rolling(20).mean()
///     .zip_combine(prices.rolling(50).mean(), (f, s) => f > s);
/// let positions = signals.simulate(state_tracker, { initial_state: 0 });
/// ```
///
/// ## Cross-Domain Applications
///
/// - Physics: State machines for PDE boundary conditions
/// - Signal Processing: Edge detection state, filter initialization
/// - IoT: Device state tracking, alert accumulation
/// - Finance: Position tracking, order state management

// ===== Single Table Simulation =====

/// Simulate a state machine over a single series
///
/// The handler receives (row, state, index) and should return either:
/// - The new state directly
/// - { state: newState, result: optionalResult } for collecting results
///
/// @param table - The data table to iterate over
/// @param handler - Function (row, state, index) => newState or { state, result }
/// @param config - Optional configuration object:
///   - initial_state: Initial state value (default: {})
///   - on_complete: Optional callback when simulation completes
///   - batch_size: For chunked processing (default: 0 = all at once)
///   - collect_results: Whether to collect results (default: true)
///   - mode: "full" for row access, "signal" for numeric values
///
/// @returns { final_state, results, elements_processed }
///
/// @example
/// let result = prices.simulate(
///     (row, state, idx) => {
///         if row.close > state.threshold {
///             { state: { ...state, position: 1 }, result: "buy" }
///         } else {
///             state
///         }
///     },
///     { initial_state: { position: 0, threshold: 100.0 } }
/// );
// Note: simulate() is a method on DataTable, not a standalone function.
// Usage: table.simulate(handler, config)

// ===== Multi-Table Correlated Simulation =====

/// Simulate a state machine over multiple aligned series
///
/// The handler receives (context, state, index) where context is an object
/// containing the current value from each named series.
///
/// @param tables - Object mapping names to tables: { "spy": spy_data, "vix": vix_data }
/// @param handler - Function (context, state, index) => newState or { state, result }
/// @param config - Optional configuration (same as simulate())
///
/// @returns { final_state, results, elements_processed }
///
/// @example
/// let result = simulate_correlated(
///     { spy: spy_prices, vix: vix_prices },
///     (ctx, state, idx) => {
///         let spread = ctx.spy - ctx.vix * 10;
///         if spread > 50 && state.position == 0 {
///             { state: { position: 1 }, result: "enter_long" }
///         } else if spread < -20 && state.position == 1 {
///             { state: { position: 0 }, result: "exit" }
///         } else {
///             state
///         }
///     },
///     { initial_state: { position: 0 } }
/// );
///
/// @note All series must have the same length (aligned timestamps)
/// @note JIT: Table names are resolved to indices at compile time for performance
// simulate_correlated is a built-in function, not exported from self module.
// Usage: simulate_correlated(series_map, handler, config)

// ===== Configuration Helpers =====

type SimulationConfig {
    initial_state: object,
    mode: string,
    collect_results: bool,
    collect_event_log: bool
}

type SimulationResult {
    final_state: object,
    results: array,
    elements_processed: int
}

/// Create a simulation config with signal mode
/// Signal mode is optimized for pre-computed SIMD signals
pub fn signal_mode_config(initial_state = {}) -> SimulationConfig {
    {
        initial_state: initial_state,
        mode: "signal",
        collect_results: true,
        collect_event_log: false
    }
}

/// Create a simulation config with full row access mode
pub fn full_mode_config(initial_state = {}) -> SimulationConfig {
    {
        initial_state: initial_state,
        mode: "full",
        collect_results: true,
        collect_event_log: false
    }
}

/// Create a simulation config without result collection
/// Use self when you only care about final state (saves memory)
pub fn state_only_config(initial_state = {}) -> SimulationConfig {
    {
        initial_state: initial_state,
        mode: "full",
        collect_results: false,
        collect_event_log: false
    }
}

// ===== Result Utilities =====

/// Extract trades/events from simulation results
pub fn get_results(sim_result: SimulationResult) {
    sim_result.results
}

/// Get the final state from simulation results
pub fn get_final_state(sim_result: SimulationResult) {
    sim_result.final_state
}

/// Get count of processed elements
pub fn get_processed_count(sim_result: SimulationResult) {
    sim_result.elements_processed
}

// ===== Replay =====

/// Replay a simulation with event log collection enabled
///
/// Re-runs a simulation on the given table using the provided handler and config,
/// with `collect_event_log: true` forced on. Useful for deterministic replay
/// and debugging simulation behavior.
///
/// @param table - The data table to simulate over
/// @param handler - Function (row, state, index) => { state, result, event_type }
/// @param config - Simulation config (collect_event_log will be forced true)
/// @returns Simulation result with event_log array
pub fn replay(table, handler, config: SimulationConfig) {
    let replay_config: SimulationConfig = {
        ...config,
        collect_event_log: true
    };
    table.simulate(handler, replay_config)
}