code wiki / (root) / sketch_holt.nx

sketch_holt.nx source

↩ module page · 133 lines · 4189 B

1// sketch_holt.nx -- Holt's linear method (double exponential smoothing). 2// 3// Time-series forecasting primitive that extends EWMA with a TREND 4// component. Two recurrences: 5// 6// level_t = alpha * y_t + (1 - alpha) * (level_{t-1} + trend_{t-1}) 7// trend_t = beta * (level_t - level_{t-1}) + (1 - beta) * trend_{t-1} 8// 9// forecast(h) = level_t + h * trend_t (h steps ahead) 10// 11// alpha controls level smoothing; beta controls trend smoothing. 12// Both in PPM [1, 1_000_000]. 13// 14// COMPLEMENTS EWMA (single-component): 15// - EWMA: smoothed level only; bad for trending data 16// - Holt: level + trend; tracks linear trajectories 17// - (Holt-Winters with seasonality is the natural v2) 18// 19// USE CASES: 20// - revenue / user-count forecasting 21// - SRE: capacity planning given growth trends 22// - sensor calibration drift detection 23// 24// INTEGER FIXED-POINT IMPLEMENTATION (no f64): 25// level, trend stored as i64. Updates via PPM arithmetic. 26// Overflow budget: |y| < 2^32 to keep alpha * y in i64. 27// 28// LOSSLESS-LANGUAGE DISCIPLINE: Production tier; envelope NX_ENV_ABS 29// with param_a = 1 (quantization error per step) and conf 1e9. 30 31import "syscalls.nx" 32import "sketch_types.nx" 33 34const NX_HOLT_PPM_MAX: i64 = 1000000 35const NX_HOLT_PPM_MIN: i64 = 1 36 37struct Holt { 38 alpha_ppm: i64, 39 beta_ppm: i64, 40 level: i64, 41 trend: i64, 42 count: i64, 43 has_data: i64, 44} 45 46// === construction ================================================= 47 48func nx_holt_alloc(alpha_ppm: i64, beta_ppm: i64) -> *Holt { 49 if alpha_ppm < NX_HOLT_PPM_MIN { return 0 as *Holt } 50 if alpha_ppm > NX_HOLT_PPM_MAX { return 0 as *Holt } 51 if beta_ppm < NX_HOLT_PPM_MIN { return 0 as *Holt } 52 if beta_ppm > NX_HOLT_PPM_MAX { return 0 as *Holt } 53 let raw: *u8 = sys_mmap(48) 54 let h: *Holt = raw as *Holt 55 h.alpha_ppm = alpha_ppm 56 h.beta_ppm = beta_ppm 57 h.level = 0 58 h.trend = 0 59 h.count = 0 60 h.has_data = 0 61 return h 62} 63 64// === add ========================================================== 65// 66// First sample: initialize level to y, trend to 0. 67// Second sample: trend = y - level_prev; level updated normally. 68// Subsequent: full Holt recurrence. 69 70func nx_holt_add(h: *Holt, y: i64) -> i64 { 71 if h.has_data == 0 { 72 h.level = y 73 h.trend = 0 74 h.count = 1 75 h.has_data = 1 76 return 0 77 } 78 let prev_level: i64 = h.level 79 let alpha: i64 = h.alpha_ppm 80 let one_minus_alpha: i64 = NX_HOLT_PPM_MAX - alpha 81 let beta: i64 = h.beta_ppm 82 let one_minus_beta: i64 = NX_HOLT_PPM_MAX - beta 83 84 // level_t = alpha * y + (1-alpha) * (level_{t-1} + trend_{t-1}) 85 let level_carry: i64 = prev_level + h.trend 86 let new_level: i64 = (alpha * y + one_minus_alpha * level_carry) / NX_HOLT_PPM_MAX 87 88 // trend_t = beta * (level_t - level_{t-1}) + (1-beta) * trend_{t-1} 89 let level_delta: i64 = new_level - prev_level 90 let new_trend: i64 = (beta * level_delta + one_minus_beta * h.trend) / NX_HOLT_PPM_MAX 91 92 h.level = new_level 93 h.trend = new_trend 94 h.count = h.count + 1 95 return 0 96} 97 98// === queries ====================================================== 99 100func nx_holt_level(h: *Holt) -> i64 { 101 return h.level 102} 103 104func nx_holt_trend(h: *Holt) -> i64 { 105 return h.trend 106} 107 108// Forecast h steps ahead. 109func nx_holt_forecast(holt: *Holt, h: i64) -> i64 { 110 return holt.level + h * holt.trend 111} 112 113func nx_holt_count(h: *Holt) -> i64 { 114 return h.count 115} 116 117// === typed envelope =============================================== 118// 119// Forecasts inherit cumulative quantization error from each recurrence 120// step. We declare param_a = h (forecast horizon -- error grows 121// linearly with steps ahead). 122 123func nx_holt_query_forecast(holt: *Holt, h: i64) -> *ApproxI64 { 124 let v: i64 = nx_holt_forecast(holt, h) 125 return nx_approx_new(v, NX_ENV_ABS, h, 126 1000000000, 127 NX_MATURITY_PRODUCTION, 128 NX_ADV_HONEST) 129} 130 131func nx_holt_memory_bytes(h: *Holt) -> i64 { 132 return 48 133}