code wiki / (root) / nx_forecast.nx

nx_forecast.nx source

↩ module page · 151 lines · 4081 B

1// nx_forecast.nx -- time-series forecasting primitives. 2// 3// Classical recursive forecasters complementing nx_kalman: 4// 5// 1. Simple Exponential Smoothing (Brown 1956) 6// s_t = alpha * x_t + (1 - alpha) * s_{t-1} 7// 8// 2. Holt Double Exponential (Holt 1957) 9// l_t = alpha * x_t + (1 - alpha) * (l_{t-1} + b_{t-1}) 10// b_t = beta * (l_t - l_{t-1}) + (1 - beta) * b_{t-1} 11// forecast(h) = l_t + h * b_t 12// 13// 3. Autoregressive AR(p) forecaster 14// x_t = phi_1 x_{t-1} + ... + phi_p x_{t-p} 15// 16// All smoothing parameters in Q14 fixed-point. Pure i64. 17// 18// What this unlocks: 19// + sensor smoothing without state-model (lighter than Kalman) 20// + trend extrapolation 21// + AR-model rollout for short-horizon prediction 22// + anomaly detection (deviation from forecast) 23// 24// Sibling of nx_ts.nx (which handles Unix-epoch <-> calendar); 25// this module forecasts numeric time series. 26// 27// genealogy_id: brown_1956_ses + holt_1957_double_exp + box_jenkins_1970 28// lineage_id: recursive_exponential_smoother + autoregressive_predictor 29 30// nx_safety_envelope: 31// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 32// sil_target: SIL1 33// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 34// verdict: NOT_YET_EVALUATED 35 36import "syscalls.nx" 37 38const NX_FORECAST_Q: i64 = 16384 // Q14 39 40// ===== Simple Exponential Smoothing ==================================== 41 42struct SES { 43 s: i64, 44 alpha: i64, 45 init: i64, 46} 47 48func nx_forecast_ses_init(ses: *SES, alpha_q14: i64) -> i64 { 49 ses.s = 0 50 ses.alpha = alpha_q14 51 ses.init = 0 52 return 0 53} 54 55func nx_forecast_ses_observe(ses: *SES, x: i64) -> i64 { 56 if ses.init == 0 { 57 ses.s = x 58 ses.init = 1 59 return 0 60 } 61 let a: i64 = ses.alpha 62 ses.s = (a * x + (NX_FORECAST_Q - a) * ses.s) / NX_FORECAST_Q 63 return 0 64} 65 66func nx_forecast_ses_predict(ses: *SES) -> i64 { 67 return ses.s 68} 69 70// ===== Holt Double Exponential (level + trend) ========================= 71 72struct Holt { 73 l: i64, 74 b: i64, 75 alpha: i64, 76 beta: i64, 77 init_count: i64, 78 prev: i64, 79} 80 81func nx_forecast_holt_init(h: *Holt, alpha_q14: i64, beta_q14: i64) -> i64 { 82 h.l = 0 83 h.b = 0 84 h.alpha = alpha_q14 85 h.beta = beta_q14 86 h.init_count = 0 87 h.prev = 0 88 return 0 89} 90 91func nx_forecast_holt_observe(h: *Holt, x: i64) -> i64 { 92 if h.init_count == 0 { 93 h.prev = x 94 h.init_count = 1 95 return 0 96 } 97 if h.init_count == 1 { 98 h.l = x 99 h.b = x - h.prev 100 h.init_count = 2 101 return 0 102 } 103 let a: i64 = h.alpha 104 let bt: i64 = h.beta 105 let prev_l: i64 = h.l 106 let new_l: i64 = (a * x + (NX_FORECAST_Q - a) * (prev_l + h.b)) / NX_FORECAST_Q 107 let new_b: i64 = (bt * (new_l - prev_l) + (NX_FORECAST_Q - bt) * h.b) / NX_FORECAST_Q 108 h.l = new_l 109 h.b = new_b 110 return 0 111} 112 113func nx_forecast_holt_predict(h: *Holt) -> i64 { 114 return h.l + h.b 115} 116 117func nx_forecast_holt_predict_h(h: *Holt, steps: i64) -> i64 { 118 return h.l + steps * h.b 119} 120 121// ===== AR(p) prediction (coefficients in Q14) ========================== 122// 123// history[0] = x_{t-1}, history[1] = x_{t-2}, ... 124// coeffs_q14[0] = phi_1, coeffs_q14[1] = phi_2, ... 125 126func nx_forecast_ar_predict(history: *i64, coeffs_q14: *i64, p: i64) -> i64 { 127 var acc: i64 = 0 128 var i: i64 = 0 129 while i < p { 130 acc = acc + coeffs_q14[i] * history[i] 131 i = i + 1 132 } 133 return acc / NX_FORECAST_Q 134} 135 136func nx_forecast_ar_rollout(history: *i64, coeffs_q14: *i64, p: i64, 137 n_steps: i64, out_forecasts: *i64) -> i64 { 138 var step: i64 = 0 139 while step < n_steps { 140 let next: i64 = nx_forecast_ar_predict(history, coeffs_q14, p) 141 out_forecasts[step] = next 142 var j: i64 = p - 1 143 while j > 0 { 144 history[j] = history[j - 1] 145 j = j - 1 146 } 147 history[0] = next 148 step = step + 1 149 } 150 return 0 151}