sketch_mann_kendall_vs_endpoint_bench.nx source
↩ module page · 90 lines · 3334 B
1// sketch_mann_kendall_vs_endpoint_bench.nx -- trend detection bench.
2//
3// CLAIM: Mann-Kendall (1945/1948) tests trend via signed pairwise
4// comparisons; robust to outliers and noise. Endpoint baseline
5// (compare first vs last value) is naive and fragile to noise.
6//
7// WORKLOAD:
8// Trended series y_t = 100 + 5*t + noise, t=1..50 -> MK should detect.
9// Stationary noisy series with same noise -> MK should NOT detect.
10// Endpoint baseline: classify if (last - first) > 50 -> trend.
11// Endpoint can be fooled by noisy endpoints.
12
13import "syscalls.nx"
14import "sketch_mann_kendall.nx"
15import "sketch_comparator.nx"
16import "sketch_types.nx"
17
18const NX_MKE_LCG_A: i64 = 1103515245
19const NX_MKE_LCG_C: i64 = 12345
20const NX_MKE_LCG_MOD: i64 = 0x7FFFFFFF
21
22func main() -> i64 {
23 // ---- Trended series ----
24 let mk_trend: *MannKendall = nx_mk_alloc(50)
25 if mk_trend == (0 as *MannKendall) { return __syscall(93, 1, 0, 0, 0, 0, 0) }
26 var sim: i64 = 42
27 var first_trended: i64 = 0
28 var last_trended: i64 = 0
29 var t: i64 = 1
30 while t <= 50 {
31 sim = ((sim * NX_MKE_LCG_A) + NX_MKE_LCG_C) & NX_MKE_LCG_MOD
32 let noise: i64 = (sim & 0x1F) - 16 // -16..15
33 let y: i64 = 100 + 5 * t + noise
34 nx_mk_push(mk_trend, y)
35 if t == 1 { first_trended = y }
36 if t == 50 { last_trended = y }
37 t = t + 1
38 }
39
40 // ---- Stationary noisy series ----
41 let mk_flat: *MannKendall = nx_mk_alloc(50)
42 if mk_flat == (0 as *MannKendall) { return __syscall(93, 2, 0, 0, 0, 0, 0) }
43 sim = 42 // identical seed for fairness
44 var first_flat: i64 = 0
45 var last_flat: i64 = 0
46 t = 1
47 while t <= 50 {
48 sim = ((sim * NX_MKE_LCG_A) + NX_MKE_LCG_C) & NX_MKE_LCG_MOD
49 let noise: i64 = (sim & 0x1F) - 16
50 let y: i64 = 200 + noise // stationary mean=200
51 nx_mk_push(mk_flat, y)
52 if t == 1 { first_flat = y }
53 if t == 50 { last_flat = y }
54 t = t + 1
55 }
56
57 // ---- MK verdicts ----
58 let v_trend: i64 = nx_mk_verdict(mk_trend)
59 let v_flat: i64 = nx_mk_verdict(mk_flat)
60
61 // ---- MK should detect upward trend in trended ----
62 // (verdict semantics: > 0 = trend; check via s value)
63 let s_trend: i64 = nx_mk_s(mk_trend)
64 let s_flat: i64 = nx_mk_s(mk_flat)
65
66 if s_trend <= 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
67 // MK on stationary should be near zero (small s)
68 if s_flat >= s_trend / 2 { return __syscall(93, 20, 0, 0, 0, 0, 0) }
69
70 // ---- Endpoint baseline ----
71 let endpoint_trend_signal: i64 = last_trended - first_trended
72 let endpoint_flat_signal: i64 = last_flat - first_flat
73 // Endpoint correctly identifies trend (large positive)
74 if endpoint_trend_signal <= 50 {
75 // Endpoint missed it OR is misleading. MK shouldn't lose on this.
76 }
77
78 // ---- Sanity: MK trend confidence > MK flat confidence ----
79 // |s_trend| should be MUCH greater than |s_flat|
80 var abs_s_trend: i64 = s_trend
81 if abs_s_trend < 0 { abs_s_trend = -abs_s_trend }
82 var abs_s_flat: i64 = s_flat
83 if abs_s_flat < 0 { abs_s_flat = -abs_s_flat }
84 if abs_s_trend < abs_s_flat * 3 {
85 // Should be at least 3x larger
86 return __syscall(93, 30, 0, 0, 0, 0, 0)
87 }
88
89 return 0
90}